File Coverage

lib/XML/DOM/Lite/XPath.pm
Criterion Covered Total %
statement 383 947 40.4
branch 115 290 39.6
condition 32 102 31.3
subroutine 74 180 41.1
pod 0 55 0.0
total 604 1574 38.3


line stmt bran cond sub pod time code
1             package XML::DOM::Lite::XPath;
2 8     8   43 use warnings;
  8         17  
  8         203  
3 8     8   34 use strict;
  8         12  
  8         833  
4              
5 8     8   4208 use XML::DOM::Lite::NodeList;
  8         18  
  8         201  
6 8     8   49 use XML::DOM::Lite::Constants qw(:nodeTypes);
  8         14  
  8         7657  
7              
8             #============ Innter Packages ============
9             package XML::DOM::Lite::XPath::ExprContext;
10              
11             sub new {
12 63     63   99 my ($class, $node, $position, $nodelist, $parent) = @_;
13             return bless {
14             node => $node,
15             position => $position,
16             nodelist => $nodelist,
17             variables => { },
18             parent => $parent,
19 63 100       275 root => $parent ? $parent->{root} : $node->ownerDocument
20             }, $class;
21             }
22              
23             sub clone {
24 60     60   92 my ($self, $node, $position, $nodelist) = @_;
25             return XML::DOM::Lite::XPath::ExprContext->new(
26             defined $node ? $node : $self->{node},
27             defined $position ? $position : $self->{position},
28             defined $nodelist ? $nodelist : $self->{nodelist},
29 60 100       191 $self);
    100          
    100          
30             }
31              
32             sub setVariable {
33 0     0   0 my ($self, $name, $value) = @_;
34 0         0 $self->{variables}->{name} = $value;
35             }
36              
37             sub getVariable {
38 0     0   0 my ($self, $name) = @_;
39 0 0       0 if (defined $self->{variables}->{name}) {
    0          
40 0         0 return $self->{variables}->{name};
41              
42             } elsif ($self->{parent}) {
43 0         0 return $self->{parent}->getVariable($name);
44              
45             } else {
46 0         0 return undef;
47             }
48             }
49              
50             sub setNode {
51 0     0   0 my ($self, $node, $position) = @_;
52 0         0 $self->{node} = $node;
53 0         0 $self->{position} = $position;
54             }
55              
56             package XML::DOM::Lite::XPath::StringValue;
57             sub new {
58 2     2   6 my ($class, $value) = @_;
59 2         8 return bless {
60             value => $value,
61             type => 'string',
62             }, $class;
63             }
64              
65             sub stringValue {
66 2     2   5 return $_[0]->{value};
67             }
68              
69             sub booleanValue {
70 0     0   0 return length($_[0]->{value}) > 0;
71             }
72              
73             sub numberValue {
74 0     0   0 return $_[0]->{value} - 0;
75             }
76              
77             sub nodeSetValue {
78 0     0   0 die $_[0];
79             }
80              
81             package XML::DOM::Lite::XPath::BooleanValue;
82             sub new {
83 22     22   40 my ($class, $value) = @_;
84 22         99 return bless {
85             value => $value,
86             type => 'boolean'
87             }, $class;
88             }
89              
90             sub stringValue {
91 0     0   0 return ''.$_[0]->{value};
92             }
93              
94             sub booleanValue {
95 26     26   82 return $_[0]->{value};
96             }
97              
98             sub numberValue {
99 0 0   0   0 return $_[0]->{value} ? 1 : 0;
100             }
101              
102             sub nodeSetValue {
103 0     0   0 die $_[0] . ' ';
104             }
105              
106             package XML::DOM::Lite::XPath::NumberValue;
107             sub new {
108 0     0   0 my ($class, $value) = @_;
109 0         0 return bless {
110             value => $value,
111             type => 'number'
112             }, $class;
113             }
114              
115             sub stringValue {
116 0     0   0 return '' . $_[0]->{value};
117             }
118              
119             sub booleanValue {
120 0     0   0 return not not $_[0]->{value};
121             }
122              
123             sub numberValue {
124 0     0   0 return $_[0]->{value} - 0;
125             }
126              
127             sub nodeSetValue {
128 0     0   0 die $_[0] . ' ';
129             }
130              
131             package XML::DOM::Lite::XPath::NodeSetValue;
132             sub new {
133 33     33   46 my ($class, $value) = @_;
134 33         116 return bless {
135             value => $value,
136             type => 'node-set'
137             }, $class;
138             }
139              
140             sub stringValue {
141 1 50   1   3 if (@{$_[0]->{value}} == 0) {
  1         4  
142 0         0 return '';
143             } else {
144 1         4 return XML::DOM::Lite::XPath::xmlValue($_[0]->{value}->[0]);
145             }
146             }
147              
148             sub booleanValue {
149 0     0   0 return $_[0]->{value}->length > 0;
150             }
151              
152             sub numberValue {
153 0     0   0 return $_[0]->stringValue() - 0;
154             }
155              
156             sub nodeSetValue {
157 30     30   52 return $_[0]->{value};
158             }
159              
160             package XML::DOM::Lite::XPath::TokenExpr;
161             sub new {
162 18     18   39 my ($class, $m) = @_;
163 18         91 return bless { value => $m }, $class;
164             }
165              
166             sub evaluate {
167 0     0   0 return XML::DOM::Lite::XPath::StringValue->new($_->{value});
168             }
169              
170             package XML::DOM::Lite::XPath::LocationExpr;
171              
172             sub new {
173 7     7   16 my ($class) = @_;
174 7         30 return bless {
175             absolute => 0,
176             steps => [ ],
177             }, $class;
178             }
179              
180             sub appendStep {
181 8     8   11 push @{$_[0]->{steps}}, $_[1];
  8         30  
182             }
183              
184             sub prependStep {
185 1     1   2 unshift @{$_[0]->{steps}}, $_[1];
  1         3  
186             }
187              
188             sub evaluate {
189 13     13   23 my ($self, $ctx) = @_;
190 13         42 my $start;
191 13 100       29 if ($self->{absolute}) {
192 4         17 $start = $ctx->{root};
193              
194             } else {
195 9         13 $start = $ctx->{node};
196             }
197              
198 13         19 my $nodes = [];
199 13         44 xPathStep($nodes, $self->{steps}, 0, $start, $ctx);
200 13         25 return XML::DOM::Lite::XPath::NodeSetValue->new($nodes);
201             }
202              
203             sub xPathStep {
204 20     20   35 my ($nodes, $steps, $step, $input, $ctx) = @_;
205 20         26 my $s = $steps->[$step];
206 20         39 my $ctx2 = $ctx->clone($input);
207 20         50 my $nodelist = $s->evaluate($ctx2)->nodeSetValue();
208              
209 20         69 for (my $i = 0; $i < @$nodelist; ++$i) {
210 17 100       36 if ($step == @$steps - 1) {
211 10         41 push @$nodes, $nodelist->[$i];
212             } else {
213 7         20 xPathStep($nodes, $steps, $step + 1, $nodelist->[$i], $ctx);
214             }
215             }
216             }
217              
218             package XML::DOM::Lite::XPath::StepExpr;
219 8     8   52 use XML::DOM::Lite::Constants qw(:nodeTypes);
  8         13  
  8         5940  
220             sub new {
221 9     9   24 my ($class, $axis, $nodetest, $predicate) = @_;
222 9   50     63 return bless {
223             axis => $axis,
224             nodetest => $nodetest,
225             predicate => $predicate || [],
226             }, $class;
227             }
228              
229             sub appendPredicate {
230 2     2   4 my ($self, $p) = @_;
231 2         3 push(@{$self->{predicate}}, $p);
  2         14  
232             }
233              
234             our $xpathAxis = {
235             ANCESTOR_OR_SELF => 'ancestor-or-self',
236             ANCESTOR => 'ancestor',
237             ATTRIBUTE => 'attribute',
238             CHILD => 'child',
239             DESCENDANT_OR_SELF => 'descendant-or-self',
240             DESCENDANT => 'descendant',
241             FOLLOWING_SIBLING => 'following-sibling',
242             FOLLOWING => 'following',
243             NAMESPACE => 'namespace',
244             PARENT => 'parent',
245             PRECEDING_SIBLING => 'preceding-sibling',
246             PRECEDING => 'preceding',
247             SELF => 'self'
248             };
249              
250             sub evaluate {
251 20     20   29 my ($self, $ctx) = @_;
252 20         28 my $input = $ctx->{node};
253 20         50 my $nodelist = XML::DOM::Lite::NodeList->new([ ]);
254              
255 20 50       124 if ($self->{axis} eq $xpathAxis->{ANCESTOR_OR_SELF}) {
    50          
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
256 0         0 push @$nodelist, $input;
257 0         0 for (my $n = $input->parentNode; $n; $n = $input->parentNode) {
258 0         0 push @$nodelist, $n;
259             }
260              
261             } elsif ($self->{axis} eq $xpathAxis->{ANCESTOR}) {
262 0         0 for (my $n = $input->parentNode; $n; $n = $input->parentNode) {
263 0         0 push @$nodelist, $n;
264             }
265              
266             } elsif ($self->{axis} eq $xpathAxis->{ATTRIBUTE}) {
267 2         3 @$nodelist = @{$input->attributes};
  2         8  
268            
269             } elsif ($self->{axis} eq $xpathAxis->{CHILD}) {
270 14         16 @$nodelist = @{$input->childNodes};
  14         39  
271              
272             } elsif ($self->{axis} eq $xpathAxis->{DESCENDANT_OR_SELF}) {
273 1         3 push @$nodelist, $input;
274 1         3 XML::DOM::Lite::XPath::xpathCollectDescendants($nodelist, $input);
275              
276             } elsif ($self->{axis} eq $xpathAxis->{DESCENDANT}) {
277 0         0 XML::DOM::Lite::XPath::xpathCollectDescendants($nodelist, $input);
278              
279             } elsif ($self->{axis} eq $xpathAxis->{FOLLOWING}) {
280 0         0 for (my $n = $input->parentNode; $n; $n = $n->parentNode) {
281 0         0 for (my $nn = $n->nextSibling; $nn; $nn = $nn->nextSibling) {
282 0         0 push @$nodelist, $nn;
283 0         0 XML::DOM::Lite::XPath::xpathCollectDescendants($nodelist, $nn);
284             }
285             }
286              
287             } elsif ($self->{axis} eq $xpathAxis->{FOLLOWING_SIBLING}) {
288 0         0 for (my $n = $input->nextSibling; $n; $n = $input->nextSibling) {
289 0         0 push @$nodelist, $n;
290             }
291              
292             } elsif ($self->{axis} eq $xpathAxis->{NAMESPACE}) {
293 0         0 warn('not implemented: axis namespace');
294              
295             } elsif ($self->{axis} eq $xpathAxis->{PARENT}) {
296 0 0       0 if ($input->parentNode) {
297 0         0 push(@$nodelist, $input->parentNode);
298             }
299              
300             } elsif ($self->{axis} eq $xpathAxis->{PRECEDING}) {
301 0         0 for (my $n = $input->parentNode; $n; $n = $n->parentNode) {
302 0         0 for (my $nn = $n->previousSibling; $nn; $nn = $nn->previousSibling) {
303 0         0 push(@$nodelist, $nn);
304 0         0 XML::DOM::Lite::XPath::xpathCollectDescendantsReverse($nodelist, $nn);
305             }
306             }
307              
308             } elsif ($self->{axis} eq $xpathAxis->{PRECEDING_SIBLING}) {
309 0         0 for (my $n = $input->previousSibling; $n; $n = $input->previousSibling) {
310 0         0 push(@$nodelist, $n);
311             }
312              
313             } elsif ($self->{axis} eq $xpathAxis->{SELF}) {
314 3         5 push(@$nodelist, $input);
315              
316             } else {
317 0         0 die 'ERROR -- NO SUCH AXIS: ' . $self->{axis};
318             }
319              
320 20         28 my $nodelist0 = $nodelist;
321 20         26 $nodelist = [];
322 20         53 for (my $i = 0; $i < @$nodelist0; ++$i) {
323 22         27 my $n = $nodelist0->[$i];
324 22 100       49 if ($self->{nodetest}->evaluate($ctx->clone($n, $i, $nodelist0))->booleanValue()) {
325 17         74 push(@$nodelist, $n);
326             }
327             }
328              
329 20         31 for (my $i = 0; $i < @{$self->{predicate}}; ++$i) {
  25         53  
330 5         8 my $nodelist0 = $nodelist;
331 5         8 $nodelist = [];
332 5         13 for (my $ii = 0; $ii < @$nodelist0; ++$ii) {
333 2         4 my $n = $nodelist0->[$ii];
334 2 50       6 if ($self->{predicate}->[$i]->evaluate($ctx->clone($n, $ii, $nodelist0))->booleanValue()) {
335 2         10 push(@$nodelist, $n);
336             }
337             }
338             }
339              
340 20         52 return XML::DOM::Lite::XPath::NodeSetValue->new($nodelist);
341             };
342              
343             package XML::DOM::Lite::XPath::NodeTestAny;
344             sub new {
345 3     3   4 my $class = shift;
346 3         13 return bless { value => XML::DOM::Lite::XPath::BooleanValue->new(1) }, $class;
347             }
348              
349             sub evaluate {
350 7     7   15 my ($self, $ctx) = @_;
351 7         21 return $self->{value};
352             }
353              
354             package XML::DOM::Lite::XPath::NodeTestElement;
355 8     8   65 use XML::DOM::Lite::Constants qw(:nodeTypes);
  8         15  
  8         1339  
356 0     0   0 sub new { return bless { }, $_[0] }
357              
358             sub evaluate {
359 0     0   0 my ($self, $ctx) = @_;
360 0         0 return XML::DOM::Lite::XPath::BooleanValue->new($ctx->{node}->{nodeType} == ELEMENT_NODE);
361             }
362              
363             package XML::DOM::Lite::XPath::NodeTestText;
364 8     8   52 use XML::DOM::Lite::Constants qw(:nodeTypes);
  8         13  
  8         1430  
365 0     0   0 sub new { return bless { }, $_[0] }
366              
367             sub evaluate {
368 0     0   0 my ($self, $ctx) = @_;
369 0         0 return XML::DOM::Lite::XPath::BooleanValue->new($ctx->{node}->{nodeType} == TEXT_NODE);
370             }
371              
372             package XML::DOM::Lite::XPath::NodeTestComment;
373 8     8   47 use XML::DOM::Lite::Constants qw(:nodeTypes);
  8         15  
  8         1306  
374 0     0   0 sub new { return bless { }, $_[0] }
375              
376             sub evaluate {
377 0     0   0 my ($self, $ctx) = @_;
378 0         0 return XML::DOM::Lite::XPath::BooleanValue->new($ctx->{node}->{nodeType} == COMMENT_NODE);
379             }
380              
381             package XML::DOM::Lite::XPath::NodeTestPI;
382 8     8   45 use XML::DOM::Lite::Constants qw(:nodeTypes);
  8         14  
  8         1469  
383             sub new {
384 0     0   0 my ($class, $target) = @_;
385 0         0 return bless { target => $target }, $class;
386             }
387              
388             sub evaluate {
389 0     0   0 my ($self, $ctx) = @_;
390             return XML::DOM::Lite::XPath::BooleanValue->new($ctx->{node}->{nodeType} == PROCESSING_INSTRUCTION_NODE and
391 0   0     0 (not $self->{target} or $ctx->{node}->{nodeName} eq $self->{target}));
392             }
393              
394             package XML::DOM::Lite::XPath::NodeTestNC;
395 8     8   47 use XML::DOM::Lite::Constants qw(:nodeTypes);
  8         14  
  8         32491  
396             sub new {
397 0     0   0 my ($class, $nsprefix) = @_;
398 0         0 return bless {
399             nsprefix => $nsprefix,
400             regex => qr/^$nsprefix:/,
401             }, $class;
402             }
403              
404             sub evaluate {
405 0     0   0 my ($self, $ctx) = @_;
406 0         0 my $n = $ctx->{node};
407 0         0 return XML::DOM::Lite::XPath::BooleanValue->new($n->{nodeName} =~ /$self->{regex}/);
408             }
409              
410             package XML::DOM::Lite::XPath::NodeTestName;
411             sub new {
412 6     6   15 my ($class, $name) = @_;
413 6         22 return bless {
414             name => $name,
415             }, $class;
416             }
417              
418             sub evaluate {
419 15     15   26 my ($self, $ctx) = @_;
420 15         58 my $n = $ctx->{node};
421 15         50 return XML::DOM::Lite::XPath::BooleanValue->new($n->{nodeName} eq $self->{name});
422             }
423              
424             package XML::DOM::Lite::XPath::PredicateExpr;
425             sub new {
426 2     2   5 my ($class, $expr) = @_;
427 2         9 return bless { expr => $expr }, $class;
428             }
429              
430             sub evaluate {
431 2     2   5 my ($self, $ctx) = @_;
432 2         10 my $v = $self->{expr}->evaluate($ctx);
433 2 50       8 if ($v->{type} eq 'number') {
434 0         0 return XML::DOM::Lite::XPath::BooleanValue->new($ctx->{position} == $v->numberValue() - 1);
435             } else {
436 2         6 return XML::DOM::Lite::XPath::BooleanValue->new($v->booleanValue());
437             }
438             }
439              
440             package XML::DOM::Lite::XPath::FunctionCallExpr;
441             require POSIX;
442             sub new {
443 0     0   0 my ($class, $name) = @_;
444 0         0 return bless { name => $name, args => [ ] }, $class;
445             }
446              
447             sub appendArg {
448 0     0   0 my ($self, $arg) = @_;
449 0         0 push @{$self->{args}}, $arg;
  0         0  
450             }
451              
452             sub evaluate {
453 0     0   0 my ($self, $ctx) = @_;
454 0         0 my $fn = '' . $self->{name}->{value};
455 0         0 my $f = $self->xpathfunctions->{$fn};
456 0 0       0 if ($f) {
457 0         0 return $f->($self, $ctx);
458             } else {
459 0         0 warn('XPath NO SUCH FUNCTION ' . $fn);
460 0         0 return XML::DOM::Lite::XPath::BooleanValue->new(0);
461             }
462             }
463              
464 0     0   0 sub round { return int($_[0] + .5 * ($_[0] <=> 0)) }
465              
466             sub assert {
467 0     0   0 my $b = shift;
468 0 0       0 die 'assertion failed' unless $b;
469             }
470              
471             sub xpathfunctions {
472             return {
473             'last'=> sub {
474 0     0   0 my ($self, $ctx) = @_;
475 0         0 assert(@{$self->{args}} == 0);
  0         0  
476 0         0 return XML::DOM::Lite::XPath::NumberValue->new(scalar(@{$ctx->{nodelist}}));
  0         0  
477             },
478              
479             'position'=> sub {
480 0     0   0 my ($self, $ctx) = @_;
481 0         0 assert(@{$self->{args}} == 0);
  0         0  
482 0         0 return XML::DOM::Lite::XPath::NumberValue->new($ctx->{position} + 1);
483             },
484              
485             'count'=> sub {
486 0     0   0 my ($self, $ctx) = @_;
487 0         0 assert(@{$self->{args}} == 1);
  0         0  
488 0         0 my $v = $self->{args}->[0]->evaluate($ctx);
489 0         0 return XML::DOM::Lite::XPath::NumberValue->new(scalar(@{$v->nodeSetValue()}));
  0         0  
490             },
491              
492             'id'=> sub {
493 0     0   0 my ($self, $ctx) = @_;
494 0         0 assert(@{$self->{args}} == 1);
  0         0  
495 0         0 my $e = $self->{args}->evaluate($ctx);
496 0         0 my $ret = [];
497 0         0 my $ids;
498 0 0       0 if ($e->{type} eq 'node-set') {
499 0         0 $ids = [];
500 0         0 for (my $i = 0; $i < @$e; ++$i) {
501 0         0 my $v = XML::DOM::Lite::XPath::xmlValue(split(/\s+/, $e->[$i]));
502 0         0 push @$ids, @$v;
503             }
504             } else {
505 0         0 $ids = [split(/\s+/, @$e)];
506             }
507 0         0 my $d = $ctx->{node}->ownerDocument;
508 0         0 for (my $i = 0; $i < @$ids; ++$i) {
509 0         0 my $n = $d->getElementById($ids->[$i]);
510 0 0       0 if ($n) {
511 0         0 push(@$ret, $n);
512             }
513             }
514 0         0 return XML::DOM::Lite::XPath::NodeSetValue->new($ret);
515             },
516              
517             'local-name'=> sub {
518 0     0   0 warn('not implemented yet: XPath function local-name()');
519             },
520              
521             'namespace-uri'=> sub {
522 0     0   0 warn('not implemented yet: XPath function namespace-uri()');
523             },
524              
525             'name'=> sub {
526 0     0   0 my ($self, $ctx) = @_;
527 0   0     0 assert(@{$self->{args}} == 1 or @{$self->{args}} == 0);
528 0         0 my $n;
529 0 0       0 if (@{$self->{args}} == 0) {
  0         0  
530 0         0 $n = [ $ctx->{node} ];
531             } else {
532 0         0 $n = $self->{args}->[0]->evaluate($ctx)->nodeSetValue();
533             }
534              
535 0 0       0 if (@$n == 0) {
536 0         0 return XML::DOM::Lite::XPath::StringValue->new('');
537             } else {
538 0         0 return XML::DOM::Lite::XPath::StringValue->new($n->[0]->{nodeName});
539             }
540             },
541              
542             'string'=> sub {
543 0     0   0 my ($self, $ctx) = @_;
544 0   0     0 assert(@{$self->{args}} == 1 or @{$self->{args}} == 0);
545 0 0       0 if (@{$self->{args}} == 0) {
  0         0  
546 0         0 return XML::DOM::Lite::XPath::StringValue->new(XML::DOM::Lite::XPath::NodeSetValue->new([ $ctx->{node} ])->stringValue());
547             } else {
548 0         0 return XML::DOM::Lite::XPath::StringValue->new($self->{args}->[0]->evaluate($ctx)->stringValue());
549             }
550             },
551              
552             'concat'=> sub {
553 0     0   0 my ($self, $ctx) = @_;
554 0         0 my $ret = '';
555 0         0 for (my $i = 0; $i < @{$self->{args}}; ++$i) {
  0         0  
556 0         0 $ret += $self->{args}->[$i]->evaluate($ctx)->stringValue();
557             }
558 0         0 return XML::DOM::Lite::XPath::StringValue->new($ret);
559             },
560              
561             'starts-with'=> sub {
562 0     0   0 my ($self, $ctx) = @_;
563 0         0 assert(@{$self->{args}} == 2);
  0         0  
564 0         0 my $s0 = $self->{args}->[0]->evaluate($ctx)->stringValue();
565 0         0 my $s1 = $self->{args}->[1]->evaluate($ctx)->stringValue();
566 0         0 return XML::DOM::Lite::XPath::BooleanValue->new(index($s0, $s1) == 0);
567             },
568              
569             'contains'=> sub {
570 0     0   0 my ($self, $ctx) = @_;
571 0         0 assert(@{$self->{args}} == 2);
  0         0  
572 0         0 my $s0 = $self->{args}->[0]->evaluate($ctx)->stringValue();
573 0         0 my $s1 = $self->{args}->[1]->evaluate($ctx)->stringValue();
574 0         0 return XML::DOM::Lite::XPath::BooleanValue->new(index($s0, $s1) != -1);
575             },
576              
577             'substring-before'=> sub {
578 0     0   0 my ($self, $ctx) = @_;
579 0         0 assert(@{$self->{args}} == 2);
  0         0  
580 0         0 my $s0 = $self->{args}->[0]->evaluate($ctx)->stringValue();
581 0         0 my $s1 = $self->{args}->[1]->evaluate($ctx)->stringValue();
582 0         0 my $i = index($s0, $s1);
583 0         0 my $ret;
584 0 0       0 if ($i == -1) {
585 0         0 $ret = '';
586             } else {
587 0         0 $ret = substr($s0, 0, $i);
588             }
589 0         0 return XML::DOM::Lite::XPath::StringValue->new($ret);
590             },
591              
592             'substring-after'=> sub {
593 0     0   0 my ($self, $ctx) = @_;
594 0         0 assert(@{$self->{args}} == 2);
  0         0  
595 0         0 my $s0 = $self->{args}->[0]->evaluate($ctx)->stringValue();
596 0         0 my $s1 = $self->{args}->[1]->evaluate($ctx)->stringValue();
597 0         0 my $i = index($s0, $s1);
598 0         0 my $ret;
599 0 0       0 if ($i == -1) {
600 0         0 $ret = '';
601             } else {
602 0         0 $ret = substr($s0, $i + length($s1));
603             }
604 0         0 return XML::DOM::Lite::XPath::StringValue->new($ret);
605             },
606              
607             'substring'=> sub {
608 0     0   0 my ($self, $ctx) = @_;
609 0   0     0 assert(@{$self->{args}} == 2 or @{$self->{args}} == 3);
610 0         0 my $s0 = $self->{args}->[0]->evaluate($ctx)->stringValue();
611 0         0 my $s1 = $self->{args}->[1]->evaluate($ctx)->numberValue();
612 0         0 my $ret;
613 0 0       0 if (@{$self->{args}} == 2) {
  0         0  
614 0 0       0 my $i1 = (0 <=> round($s1 - 1)) ? 0 : round($s1 - 1);
615 0         0 $ret = substr($s0, $i1);
616              
617             } else {
618 0         0 my $s2 = $self->{args}->[2]->evaluate($ctx)->numberValue();
619 0         0 my $i0 = round($s1 - 1);
620 0 0       0 my $i1 = (0 <=> $i0) ? 0 : $i0;
621 0 0       0 my $i2 = round('%d', $s2) - (0 <=> -$i0) ? 0 : -$i0;
622 0         0 $ret = substr($s0, $i1, $i2);
623             }
624 0         0 return XML::DOM::Lite::XPath::StringValue->new($ret);
625             },
626              
627             'string-length'=> sub {
628 0     0   0 my ($self, $ctx) = @_;
629 0         0 my $s;
630 0 0       0 if (@{$self->{args}} > 0) {
  0         0  
631 0         0 $s = $self->{args}->[0]->evaluate($ctx)->stringValue();
632             } else {
633 0         0 $s = XML::DOM::Lite::XPath::NodeSetValue->new([ $ctx->{node} ])->stringValue();
634             }
635 0         0 return XML::DOM::Lite::XPath::NumberValue->new(length($s));
636             },
637              
638             'normalize-space'=> sub {
639 0     0   0 my ($self, $ctx) = @_;
640 0         0 my $s;
641 0 0       0 if (@{$self->{args}} > 0) {
  0         0  
642 0         0 $s = $self->{args}->[0]->evaluate($ctx)->stringValue();
643             } else {
644 0         0 $s = XML::DOM::Lite::XPath::NodeSetValue->new([ $ctx->{node} ])->stringValue();
645             }
646 0         0 $s =~ s/^\s*//;
647 0         0 $s =~ s/\s*$//;
648 0         0 $s =~ s/\s+/ /g;
649 0         0 return XML::DOM::Lite::XPath::StringValue->new($s);
650             },
651              
652             'translate'=> sub {
653 0     0   0 my ($self, $ctx) = @_;
654 0         0 assert(@{$self->{args}} == 3);
  0         0  
655 0         0 my $s0 = $self->{args}->[0]->evaluate($ctx)->stringValue();
656 0         0 my $s1 = $self->{args}->[1]->evaluate($ctx)->stringValue();
657 0         0 my $s2 = $self->{args}->[2]->evaluate($ctx)->stringValue();
658              
659 0         0 for (my $i = 0; $i < length($s1); ++$i) {
660 0         0 my $chr1 = substr($s1, $i, 1);
661 0         0 my $chr2 = substr($s2, $i, 1);
662 0         0 $s0 =~ s/$chr1/$chr2/g;
663             }
664 0         0 return XML::DOM::Lite::XPath::StringValue->new($s0);
665             },
666              
667             'boolean'=> sub {
668 0     0   0 my ($self, $ctx) = @_;
669 0         0 assert(@{$self->{args}} == 1);
  0         0  
670 0         0 return XML::DOM::Lite::XPath::BooleanValue->new($self->{args}->[0]->evaluate($ctx)->booleanValue());
671             },
672              
673             'not'=> sub {
674 0     0   0 my ($self, $ctx) = @_;
675 0         0 assert(@{$self->{args}} == 1);
  0         0  
676 0         0 my $ret = not $self->{args}->[0]->evaluate($ctx)->booleanValue();
677 0         0 return XML::DOM::Lite::XPath::BooleanValue->new($ret);
678             },
679              
680             'true'=> sub {
681 0     0   0 my ($self, $ctx) = @_;
682 0         0 assert(@{$self->{args}} == 0);
  0         0  
683 0         0 return XML::DOM::Lite::XPath::BooleanValue->new(1);
684             },
685              
686             'false'=> sub {
687 0     0   0 my ($self, $ctx) = @_;
688 0         0 assert(@{$self->{args}} == 0);
  0         0  
689 0         0 return XML::DOM::Lite::XPath::BooleanValue->new(0);
690             },
691              
692             'lang'=> sub {
693 0     0   0 my ($self, $ctx) = @_;
694 0         0 assert(@{$self->{args}} == 1);
  0         0  
695 0         0 my $lang = $self->{args}->[0]->evaluate($ctx)->stringValue();
696 0         0 my $xmllang;
697 0         0 my $n = $ctx->{node};
698 0   0     0 while ($n && $n != $n->parentNode) {
699 0         0 $xmllang = $n->getAttribute('xml:lang');
700 0 0       0 if ($xmllang) {
701 0         0 last;
702             }
703 0         0 $n = $n->parentNode;
704             }
705 0 0       0 if (not $xmllang) {
706 0         0 return XML::DOM::Lite::XPath::BooleanValue->new(1);
707             } else {
708 0         0 my $re = qr/^$lang$/i;
709 0   0     0 return XML::DOM::Lite::XPath::BooleanValue->new($xmllang =~ /$re/ or ($xmllang =~ s/_.*$//) =~ /$re/);
710             }
711             },
712              
713             'number'=> sub {
714 0     0   0 my ($self, $ctx) = @_;
715 0   0     0 assert(@{$self->{args}} == 1 || @{$self->{args}} == 0);
716              
717 0 0       0 if (@{$self->{args}} == 1) {
  0         0  
718 0         0 return XML::DOM::Lite::XPath::NumberValue->new($self->{args}->[0]->evaluate($ctx)->numberValue());
719             } else {
720 0         0 return XML::DOM::Lite::XPath::NumberValue(XML::DOM::Lite::XPath::NodeSetValue->new([ $ctx->{node} ])->numberValue());
721             }
722             },
723              
724             'sum'=> sub {
725 0     0   0 my ($self, $ctx) = @_;
726 0         0 assert(@{$self->{args}} == 1);
  0         0  
727 0         0 my $n = $self->{args}->[0]->evaluate($ctx)->nodeSetValue();
728 0         0 my $sum = 0;
729 0         0 for (my $i = 0; $i < @$n; ++$i) {
730 0         0 $sum .= XML::DOM::Lite::XPath::xmlValue($n->[$i]) - 0;
731             }
732 0         0 return XML::DOM::Lite::XPath::NumberValue->new($sum);
733             },
734              
735             'floor'=> sub {
736 0     0   0 my ($self, $ctx) = @_;
737 0         0 assert(@{$self->{args}} == 1);
  0         0  
738 0         0 my $num = $self->{args}->[0]->evaluate($ctx)->numberValue();
739 0         0 return XML::DOM::Lite::XPath::NumberValue->new(POSIX::floor($num));
740             },
741              
742             'ceiling'=> sub {
743 0     0   0 my ($self, $ctx) = @_;
744 0         0 assert(@{$self->{args}} == 1);
  0         0  
745 0         0 my $num = $self->{args}->[0]->evaluate($ctx)->numberValue();
746 0         0 return XML::DOM::Lite::XPath::NumberValue->new(POSIX::ceil($num));
747             },
748              
749             'round'=> sub {
750 0     0   0 my ($self, $ctx) = @_;
751 0         0 assert(@{$self->{args}} == 1);
  0         0  
752 0         0 my $num = $self->{args}->[0]->evaluate($ctx)->numberValue();
753 0         0 return XML::DOM::Lite::XPath::NumberValue->new(round($num));
754             },
755              
756             'ext-join'=> sub {
757 0     0   0 my ($self, $ctx) = @_;
758 0         0 assert(@{$self->{args}} == 2);
  0         0  
759 0         0 my $nodes = $self->{args}->[0]->evaluate($ctx)->nodeSetValue();
760 0         0 my $delim = $self->{args}->[0]->evaluate($ctx)->stringValue();
761 0         0 my $ret = '';
762 0         0 for (my $i = 0; $i < @$nodes; ++$i) {
763 0 0       0 if ($ret) {
764 0         0 $ret .= $delim;
765             }
766 0         0 $ret .= XML::DOM::Lite::XPath::xmlValue($nodes->[$i]);
767             }
768 0         0 return XML::DOM::Lite::XPath::StringValue->new($ret);
769             },
770              
771             'ext-if'=> sub {
772 0     0   0 my ($self, $ctx) = @_;
773 0         0 assert(@{$self->{args}} == 3);
  0         0  
774 0 0       0 if ($self->{args}->[0]->evaluate($ctx)->booleanValue()) {
775 0         0 return $self->{args}->[1]->evaluate($ctx);
776             } else {
777 0         0 return $self->{args}->[2]->evaluate($ctx);
778             }
779             },
780              
781             'ext-sprintf' => sub {
782 0     0   0 my ($self, $ctx) = @_;
783 0         0 assert(@{$self->{args}} >= 1);
  0         0  
784 0         0 my $args = [];
785 0         0 for (my $i = 0; $i < @{$self->{args}}; ++$i) {
  0         0  
786 0         0 push(@$args, $self->{args}->[$i]->evaluate($ctx)->stringValue());
787             }
788 0         0 return XML::DOM::Lite::XPath::StringValue->new(sprintf(@$args));
789             },
790              
791             'ext-cardinal'=> sub {
792 0     0   0 my ($self, $ctx) = @_;
793 0         0 assert(@{$self->{args}} >= 1);
  0         0  
794 0         0 my $c = $self->{args}->[0]->evaluate($ctx)->numberValue();
795 0         0 my $ret = [];
796 0         0 for (my $i = 0; $i < $c; ++$i) {
797 0         0 push(@$ret, $ctx->{node});
798             }
799 0         0 return XML::DOM::Lite::XPath::NodeSetValue->new($ret);
800             }
801 0     0   0 };
802             }
803              
804             package XML::DOM::Lite::XPath::UnionExpr;
805             sub new {
806 0     0   0 my ($class, $expr1, $expr2) = @_;
807 0         0 return bless { expr1 => $expr1, expr2 => $expr2 }, $class;
808             }
809              
810             sub evaluate {
811 0     0   0 my ($self, $ctx) = @_;
812 0         0 my $nodes1 = $self->{expr1}->evaluate($ctx)->nodeSetValue();
813 0         0 my $nodes2 = $self->{expr2}->evaluate($ctx)->nodeSetValue();
814 0         0 my $I1 = scalar(@$nodes1);
815 0         0 for (my $i2 = 0; $i2 < @$nodes2; ++$i2) {
816 0         0 for (my $i1 = 0; $i1 < $I1; ++$i1) {
817 0 0       0 if ($nodes1->[$i1] == $nodes2->[$i2]) {
818 0         0 $i1 = $I1;
819             }
820             }
821 0         0 push @$nodes1, $nodes2->[$i2];
822             }
823 0         0 return XML::DOM::Lite::XPath::NodeSetValue->new($nodes2);
824             }
825              
826             package XML::DOM::Lite::XPath::PathExpr;
827             sub new {
828 0     0   0 my ($class, $filter, $rel) = @_;
829 0         0 return bless { filter => $filter, rel => $rel }, $class;
830             }
831              
832             sub evaluate {
833 0     0   0 my ($self, $ctx) = @_;
834 0         0 my $nodes = $self->{filter}->evaluate($ctx)->nodeSetValue();
835 0         0 my $nodes1 = [];
836 0         0 for (my $i = 0; $i < @$nodes; ++$i) {
837 0         0 my $nodes0 = $self->{rel}->evaluate($ctx->clone($nodes->[$i], $i, $nodes))->nodeSetValue();
838 0         0 push @$nodes1, @$nodes0;
839             }
840 0         0 return XML::DOM::Lite::XPath::NodeSetValue->new($nodes1);
841             }
842              
843             package XML::DOM::Lite::XPath::FilterExpr;
844             sub new {
845 0     0   0 my ($class, $expr, $predicate) = @_;
846 0         0 return bless { expr => $expr, predicate => $predicate }, $class;
847             }
848              
849             sub evaluate {
850 0     0   0 my ($self, $ctx) = @_;
851 0         0 my $nodes = $self->{expr}->evaluate($ctx)->nodeSetValue();
852 0         0 for (my $i = 0; $i < @{$self->{predicate}}; ++$i) {
  0         0  
853 0         0 my $nodes0 = $nodes;
854 0         0 $nodes = [];
855 0         0 for (my $j = 0; $j < @$nodes0; ++$j) {
856 0         0 my $n = $nodes0->[$j];
857 0 0       0 if ($self->{predicate}->[$i]->evaluate($ctx->clone($n, $j, $nodes0))->booleanValue()) {
858 0         0 push(@$nodes, $n);
859             }
860             }
861             }
862              
863 0         0 return XML::DOM::Lite::XPath::NodeSetValue->new($nodes);
864             }
865              
866             package XML::DOM::Lite::XPath::UnaryMinusExpr;
867             sub new {
868 0     0   0 my ($class, $expr) = @_;
869 0         0 return bless { expr => $expr }, $class;
870             }
871              
872             sub evaluate {
873 0     0   0 my ($self, $ctx) = @_;
874 0         0 return XML::DOM::Lite::XPath::NumberValue->new(-$self->{expr}->evaluate($ctx)->numberValue());
875             }
876              
877             package XML::DOM::Lite::XPath::BinaryExpr;
878             sub new {
879 2     2   7 my ($class, $expr1, $op, $expr2) = @_;
880 2         12 return bless { expr1 => $expr1, expr2 => $expr2, op => $op }, $class;
881             }
882              
883             sub evaluate {
884 2     2   5 my ($self, $ctx) = @_;
885 2         5 my $ret;
886 2         8 my $o = $self->{op}->{value};
887 2 50       23 if ($o eq 'or') {
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
888             $ret = XML::DOM::Lite::XPath::BooleanValue->new($self->{expr1}->evaluate($ctx)->booleanValue() ||
889 0   0     0 $self->{expr2}->evaluate($ctx)->booleanValue());
890             } elsif ($o eq 'and') {
891             $ret = XML::DOM::Lite::XPath::BooleanValue->new($self->{expr1}->evaluate($ctx)->booleanValue() &&
892 0   0     0 $self->{expr2}->evaluate($ctx)->booleanValue());
893             } elsif ($o eq '+') {
894             $ret = XML::DOM::Lite::XPath::BooleanValue->new($self->{expr1}->evaluate($ctx)->booleanValue() +
895 0         0 $self->{expr2}->evaluate($ctx)->booleanValue());
896             } elsif ($o eq '-') {
897             $ret = XML::DOM::Lite::XPath::BooleanValue->new($self->{expr1}->evaluate($ctx)->booleanValue() -
898 0         0 $self->{expr2}->evaluate($ctx)->booleanValue());
899             } elsif ($o eq '*') {
900             $ret = XML::DOM::Lite::XPath::BooleanValue->new($self->{expr1}->evaluate($ctx)->booleanValue() *
901 0         0 $self->{expr2}->evaluate($ctx)->booleanValue());
902             } elsif ($o eq 'mod') {
903             $ret = XML::DOM::Lite::XPath::BooleanValue->new($self->{expr1}->evaluate($ctx)->booleanValue() %
904 0         0 $self->{expr2}->evaluate($ctx)->booleanValue());
905             } elsif ($o eq 'div') {
906             $ret = XML::DOM::Lite::XPath::BooleanValue->new($self->{expr1}->evaluate($ctx)->booleanValue() /
907 0         0 $self->{expr2}->evaluate($ctx)->booleanValue());
908             } elsif ($o eq '=') {
909 2     0   14 $ret = $self->compare($ctx, sub { my ($x1, $x2) = @_; return $x1 == $x2; });
  0         0  
  0         0  
910             } elsif ($o eq '!=') {
911 0     0   0 $ret = $self->compare($ctx, sub { my ($x1, $x2) = @_; return $x1 != $x2; });
  0         0  
  0         0  
912             } elsif ($o eq '<') {
913 0     0   0 $ret = $self->compare($ctx, sub { my ($x1, $x2) = @_; return $x1 < $x2; });
  0         0  
  0         0  
914             } elsif ($o eq '<=') {
915 0     0   0 $ret = $self->compare($ctx, sub { my ($x1, $x2) = @_; return $x1 <= $x2; });
  0         0  
  0         0  
916             } elsif ($o eq '>') {
917 0     0   0 $ret = $self->compare($ctx, sub { my ($x1, $x2) = @_; return $x1 > $x2; });
  0         0  
  0         0  
918             } elsif ($o eq '>=') {
919 0     0   0 $ret = $self->compare($ctx, sub { my ($x1, $x2) = @_; return $x1 >= $x2; });
  0         0  
  0         0  
920             } else {
921 0         0 warn('BinaryExpr->evaluate: ' . $self->{op}->{value});
922             }
923 2         11 return $ret;
924             }
925              
926             sub compare {
927 2     2   5 my ($self, $ctx, $cmp) = @_;
928 2         9 my $v1 = $self->{expr1}->evaluate($ctx);
929 2         8 my $v2 = $self->{expr2}->evaluate($ctx);
930              
931 2         5 my $ret;
932 2 50 33     27 if ($v1->{type} eq 'node-set' and $v2->{type} eq 'node-set') {
    50 33        
    0 0        
    0 0        
933 0         0 my $n1 = $v1->nodeSetValue();
934 0         0 my $n2 = $v2->nodeSetValue();
935 0         0 $ret = 0;
936 0         0 for (my $i1 = 0; $i1 < @$n1; ++$i1) {
937 0         0 for (my $i2 = 0; $i2 < @$n2; ++$i2) {
938 0 0       0 if (XML::DOM::Lite::XPath::xmlValue($n1->[$i1]) cmp XML::DOM::Lite::XPath::xmlValue($n2->[$i2])) {
939 0         0 $ret = 1;
940 0         0 $i2 = @$n2;
941 0         0 $i1 = @$n1;
942             }
943             }
944             }
945              
946             } elsif ($v1->{type} eq 'node-set' or $v2->{type} eq 'node-set') {
947              
948 2 50       17 if ($v1->{type} eq 'number') {
    50          
    50          
    50          
949 0         0 my $s = $v1->numberValue();
950 0         0 my $n = $v2->nodeSetValue();
951              
952 0         0 $ret = 0;
953 0         0 for (my $i = 0; $i < @$n; ++$i) {
954 0         0 my $nn = XML::DOM::Lite::XPath::xmlValue($n->[$i]) - 0;
955 0 0       0 if ($s cmp $nn) {
956 0         0 $ret = 1;
957 0         0 last;
958             }
959             }
960              
961             } elsif ($v2->{type} eq 'number') {
962 0         0 my $n = $v1->nodeSetValue();
963 0         0 my $s = $v2->numberValue();
964              
965 0         0 $ret = 0;
966 0         0 for (my $i = 0; $i < @$n; ++$i) {
967 0         0 my $nn = XML::DOM::Lite::XPath::xmlValue($n->[$i]) - 0;
968 0 0       0 if ($nn cmp $s) {
969 0         0 $ret = 1;
970 0         0 last;
971             }
972             }
973              
974             } elsif ($v1->{type} eq 'string') {
975 0         0 my $s = $v1->stringValue();
976 0         0 my $n = $v2->nodeSetValue();
977              
978 0         0 $ret = 0;
979 0         0 for (my $i = 0; $i < @$n; ++$i) {
980 0         0 my $nn = XML::DOM::Lite::XPath::xmlValue($n->[$i]);
981 0 0       0 if ($s cmp $nn) {
982 0         0 $ret = 1;
983 0         0 last;
984             }
985             }
986              
987             } elsif ($v2->{type} eq 'string') {
988 2         6 my $n = $v1->nodeSetValue();
989 2         6 my $s = $v2->stringValue();
990              
991 2         4 $ret = 0;
992 2         8 for (my $i = 0; $i < @$n; ++$i) {
993 2         7 my $nn = XML::DOM::Lite::XPath::xmlValue($n->[$i]);
994 2 50       9 if ($nn cmp $s) {
995 2         4 $ret = 1;
996 2         4 last;
997             }
998             }
999              
1000             } else {
1001 0         0 $ret = ($v1->booleanValue() <=> $v2->booleanValue());
1002             }
1003              
1004             } elsif ($v1->{type} eq 'boolean' or $v2->{type} eq 'boolean') {
1005 0         0 $ret = ($v1->booleanValue() <=> $v2->booleanValue());
1006              
1007             } elsif ($v1->{type} eq 'number' or $v2->{type} eq 'number') {
1008 0         0 $ret = ($v1->numberValue() <=> $v2->numberValue());
1009              
1010             } else {
1011 0         0 $ret = ($v1->stringValue() <=> $v2->stringValue());
1012             }
1013              
1014 2         7 return XML::DOM::Lite::XPath::BooleanValue->new($ret);
1015             }
1016              
1017             package XML::DOM::Lite::XPath::LiteralExpr;
1018             sub new {
1019 2     2   6 my ($class, $value) = @_;
1020 2         9 return bless { value => $value };
1021             }
1022              
1023             sub evaluate {
1024 2     2   5 my ($self, $ctx) = @_;
1025 2         17 return XML::DOM::Lite::XPath::StringValue->new($self->{value});
1026             }
1027              
1028             package XML::DOM::Lite::XPath::NumberExpr;
1029             sub new {
1030 0     0   0 my ($class, $value) = @_;
1031 0         0 return bless { value => $value };
1032             }
1033              
1034             sub evaluate {
1035 0     0   0 my ($self, $ctx) = @_;
1036 0         0 return XML::DOM::Lite::XPath::NumberValue->new($self->{value});
1037             }
1038              
1039             package XML::DOM::Lite::XPath::VariableExpr;
1040             sub new {
1041 0     0   0 my ($class, $name) = @_;
1042 0         0 return bless { name => $name }, $class;
1043             }
1044              
1045             sub evaluate {
1046 0     0   0 my ($self, $ctx) = @_;
1047 0         0 return $ctx->getVariable($self->{name});
1048             }
1049              
1050             package Array::Object;
1051              
1052 8     8   71 use overload '@{}' => \&items;
  8         14  
  8         55  
1053              
1054             sub new {
1055 597     597   715 my $class = CORE::shift;
1056 597   50     1414 my $self = bless { _array => CORE::shift || [ ] }, $class;
1057 597         870 return $self;
1058             }
1059              
1060             sub items {
1061 1442     1442   3023 CORE::shift()->{_array};
1062             }
1063              
1064             #========= XML::DOM::Lite::XPath package ===========
1065             package XML::DOM::Lite::XPath;
1066              
1067             #use Array::Object;
1068              
1069             our $DEBUG = 0;
1070              
1071             our $xpathAxis = $XML::DOM::Lite::XPath::StepExpr::xpathAxis;
1072              
1073             our $xpathAxesRe = join('|', (
1074             $xpathAxis->{ANCESTOR_OR_SELF},
1075             $xpathAxis->{ANCESTOR},
1076             $xpathAxis->{ATTRIBUTE},
1077             $xpathAxis->{CHILD},
1078             $xpathAxis->{DESCENDANT_OR_SELF},
1079             $xpathAxis->{DESCENDANT},
1080             $xpathAxis->{FOLLOWING_SIBLING},
1081             $xpathAxis->{FOLLOWING},
1082             $xpathAxis->{NAMESPACE},
1083             $xpathAxis->{PARENT},
1084             $xpathAxis->{PRECEDING_SIBLING},
1085             $xpathAxis->{PRECEDING},
1086             $xpathAxis->{SELF}
1087             ));
1088              
1089              
1090             our $TOK_PIPE = { label => "|", prec => 17, re => qr/^\|/ };
1091             our $TOK_DSLASH = { label => "//", prec => 19, re => qr/^\/\// };
1092             our $TOK_SLASH = { label => "/", prec => 30, re => qr/^\// };
1093             our $TOK_AXIS = { label => '::', prec => 20, re => qr/^::/ };
1094             our $TOK_COLON = { label => ":", prec => 1000, re => qr/^:/ };
1095             our $TOK_AXISNAME = { label => "[axis]", re => qr/^($xpathAxesRe)/ };
1096             our $TOK_PARENO = { label => "(", prec => 34, re => qr/^\(/ };
1097             our $TOK_PARENC = { label => ")", re => qr/^\)/ };
1098             our $TOK_DDOT = { label => "..", prec => 34, re => qr/^\.\./ };
1099             our $TOK_DOT = { label => ".", prec => 34, re => qr/^\./ };
1100             our $TOK_AT = { label => "@", prec => 34, re => qr/^@/ };
1101              
1102             our $TOK_COMMA = { label => ",", re => qr/^,/ };
1103              
1104             our $TOK_OR = { label => "or", prec => 10, re => qr/^or\b/ };
1105             our $TOK_AND = { label => "and", prec => 11, re => qr/^and\b/ };
1106             our $TOK_EQ = { label => "=", prec => 12, re => qr/^=/ };
1107             our $TOK_NEQ = { label => "!=", prec => 12, re => qr/^!=/ };
1108             our $TOK_GE = { label => ">=", prec => 13, re => qr/^>=/ };
1109             our $TOK_GT = { label => ">", prec => 13, re => qr/^>/ };
1110             our $TOK_LE = { label => "<=", prec => 13, re => qr/^<=/ };
1111             our $TOK_LT = { label => "<", prec => 13, re => qr/^
1112             our $TOK_PLUS = { label => "+", prec => 14, re => qr/^\+/, left => 1 };
1113             our $TOK_MINUS = { label => "-", prec => 14, re => qr/^\-/, left => 1 };
1114             our $TOK_DIV = { label => "div", prec => 15, re => qr/^div\b/, left => 1 };
1115             our $TOK_MOD = { label => "mod", prec => 15, re => qr/^mod\b/, left => 1 };
1116              
1117             our $TOK_BRACKO = { label => "[", prec => 32, re => qr/^\[/ };
1118             our $TOK_BRACKC = { label => "]", re => qr/^\]/ };
1119             our $TOK_DOLLAR = { label => '$', re => qr/^\$/ };
1120              
1121             our $TOK_NCNAME = { label => "[ncname]", re => qr/^[a-z][-\w]*/i };
1122              
1123             our $TOK_ASTERISK = { label => "*", prec => 15, re => qr/^\*/, left => 1 };
1124             our $TOK_LITERALQ = { label => "[litq]", prec => 20, re => qr/^'[^']*'/ };
1125             our $TOK_LITERALQQ = {
1126             label => "[litqq]",
1127             prec => 20,
1128             re => qr/^"[^"]*"/
1129             };
1130              
1131             our $TOK_NUMBER = {
1132             label => "[number]",
1133             prec => 35,
1134             re => qr/^\d+(\.\d*)?/
1135             };
1136              
1137             our $TOK_QNAME = {
1138             label => "[qname]",
1139             re => qr/^([a-z][-\w]*:)?[a-z][-\w]*/i
1140             };
1141              
1142             our $TOK_NODEO = {
1143             label => "[nodetest-start]",
1144             re => qr/^(processing-instruction|comment|text|node)\(/
1145             };
1146              
1147             our $xpathTokenRules = [
1148             $TOK_DSLASH,
1149             $TOK_SLASH,
1150             $TOK_DDOT,
1151             $TOK_DOT,
1152             $TOK_AXIS,
1153             $TOK_COLON,
1154             $TOK_AXISNAME,
1155             $TOK_NODEO,
1156             $TOK_PARENO,
1157             $TOK_PARENC,
1158             $TOK_BRACKO,
1159             $TOK_BRACKC,
1160             $TOK_AT,
1161             $TOK_COMMA,
1162             $TOK_OR,
1163             $TOK_AND,
1164             $TOK_NEQ,
1165             $TOK_EQ,
1166             $TOK_GE,
1167             $TOK_GT,
1168             $TOK_LE,
1169             $TOK_LT,
1170             $TOK_PLUS,
1171             $TOK_MINUS,
1172             $TOK_ASTERISK,
1173             $TOK_PIPE,
1174             $TOK_MOD,
1175             $TOK_DIV,
1176             $TOK_LITERALQ,
1177             $TOK_LITERALQQ,
1178             $TOK_NUMBER,
1179             $TOK_QNAME,
1180             $TOK_NCNAME,
1181             $TOK_DOLLAR
1182             ];
1183              
1184             our $XPathLocationPath = { label => "LocationPath" };
1185             our $XPathRelativeLocationPath = { label => "RelativeLocationPath" };
1186             our $XPathAbsoluteLocationPath = { label => "AbsoluteLocationPath" };
1187             our $XPathStep = { label => "Step" };
1188             our $XPathNodeTest = { label => "NodeTest" };
1189             our $XPathPredicate = { label => "Predicate" };
1190             our $XPathLiteral = { label => "Literal" };
1191             our $XPathExpr = { label => "Expr" };
1192             our $XPathPrimaryExpr = { label => "PrimaryExpr" };
1193             our $XPathVariableReference = { label => "Variablereference" };
1194             our $XPathNumber = { label => "Number" };
1195             our $XPathFunctionCall = { label => "FunctionCall" };
1196             our $XPathArgumentRemainder = { label => "ArgumentRemainder" };
1197             our $XPathPathExpr = { label => "PathExpr" };
1198             our $XPathUnionExpr = { label => "UnionExpr" };
1199             our $XPathFilterExpr = { label => "FilterExpr" };
1200             our $XPathDigits = { label => "Digits" };
1201              
1202             our $xpathNonTerminals = [
1203             $XPathLocationPath,
1204             $XPathRelativeLocationPath,
1205             $XPathAbsoluteLocationPath,
1206             $XPathStep,
1207             $XPathNodeTest,
1208             $XPathPredicate,
1209             $XPathLiteral,
1210             $XPathExpr,
1211             $XPathPrimaryExpr,
1212             $XPathVariableReference,
1213             $XPathNumber,
1214             $XPathFunctionCall,
1215             $XPathArgumentRemainder,
1216             $XPathPathExpr,
1217             $XPathUnionExpr,
1218             $XPathFilterExpr,
1219             $XPathDigits
1220             ];
1221              
1222             our $Q_01 = { label => "?" };
1223             our $Q_MM = { label => "*" };
1224             our $Q_1M = { label => "+" };
1225              
1226             our $ASSOC_LEFT = 1;
1227              
1228             our $xpathGrammarRules =
1229             [
1230             [ $XPathLocationPath, [ $XPathRelativeLocationPath ], 18,
1231             \&passExpr ],
1232             [ $XPathLocationPath, [ $XPathAbsoluteLocationPath ], 18,
1233             \&passExpr ],
1234              
1235             [ $XPathAbsoluteLocationPath, [ $TOK_SLASH, $XPathRelativeLocationPath ], 18,
1236             \&makeLocationExpr1 ],
1237             [ $XPathAbsoluteLocationPath, [ $TOK_DSLASH, $XPathRelativeLocationPath ], 18,
1238             \&makeLocationExpr2 ],
1239              
1240             [ $XPathAbsoluteLocationPath, [ $TOK_SLASH ], 0,
1241             \&makeLocationExpr3 ],
1242             [ $XPathAbsoluteLocationPath, [ $TOK_DSLASH ], 0,
1243             \&makeLocationExpr4 ],
1244              
1245             [ $XPathRelativeLocationPath, [ $XPathStep ], 31,
1246             \&makeLocationExpr5 ],
1247             [ $XPathRelativeLocationPath,
1248             [ $XPathRelativeLocationPath, $TOK_SLASH, $XPathStep ], 31,
1249             \&makeLocationExpr6 ],
1250             [ $XPathRelativeLocationPath,
1251             [ $XPathRelativeLocationPath, $TOK_DSLASH, $XPathStep ], 31,
1252             \&makeLocationExpr7 ],
1253              
1254             [ $XPathStep, [ $TOK_DOT ], 33,
1255             \&makeStepExpr1 ],
1256             [ $XPathStep, [ $TOK_DDOT ], 33,
1257             \&makeStepExpr2 ],
1258             [ $XPathStep,
1259             [ $TOK_AXISNAME, $TOK_AXIS, $XPathNodeTest ], 33,
1260             \&makeStepExpr3 ],
1261             [ $XPathStep, [ $TOK_AT, $XPathNodeTest ], 33,
1262             \&makeStepExpr4 ],
1263             [ $XPathStep, [ $XPathNodeTest ], 33,
1264             \&makeStepExpr5 ],
1265             [ $XPathStep, [ $XPathStep, $XPathPredicate ], 33,
1266             \&makeStepExpr6 ],
1267              
1268             [ $XPathNodeTest, [ $TOK_ASTERISK ], 33,
1269             \&makeNodeTestExpr1 ],
1270             [ $XPathNodeTest, [ $TOK_NCNAME, $TOK_COLON, $TOK_ASTERISK ], 33,
1271             \&makeNodeTestExpr2 ],
1272             [ $XPathNodeTest, [ $TOK_QNAME ], 33,
1273             \&makeNodeTestExpr3 ],
1274             [ $XPathNodeTest, [ $TOK_NODEO, $TOK_PARENC ], 33,
1275             \&makeNodeTestExpr4 ],
1276             [ $XPathNodeTest, [ $TOK_NODEO, $XPathLiteral, $TOK_PARENC ], 33,
1277             \&makeNodeTestExpr5 ],
1278              
1279             [ $XPathPredicate, [ $TOK_BRACKO, $XPathExpr, $TOK_BRACKC ], 33,
1280             \&makePredicateExpr ],
1281              
1282             [ $XPathPrimaryExpr, [ $XPathVariableReference ], 33,
1283             \&passExpr ],
1284             [ $XPathPrimaryExpr, [ $TOK_PARENO, $XPathExpr, $TOK_PARENC ], 33,
1285             \&makePrimaryExpr ],
1286             [ $XPathPrimaryExpr, [ $XPathLiteral ], 30,
1287             \&passExpr ],
1288             [ $XPathPrimaryExpr, [ $XPathNumber ], 30,
1289             \&passExpr ],
1290             [ $XPathPrimaryExpr, [ $XPathFunctionCall ], 30,
1291             \&passExpr ],
1292              
1293             [ $XPathFunctionCall, [ $TOK_QNAME, $TOK_PARENO, $TOK_PARENC ], -1,
1294             \&makeFunctionCallExpr1 ],
1295             [ $XPathFunctionCall,
1296             [ $TOK_QNAME, $TOK_PARENO, $XPathExpr, $XPathArgumentRemainder, $Q_MM,
1297             $TOK_PARENC ], -1,
1298             \&makeFunctionCallExpr2 ],
1299             [ $XPathArgumentRemainder, [ $TOK_COMMA, $XPathExpr ], -1,
1300             \&makeArgumentExpr ],
1301              
1302             [ $XPathUnionExpr, [ $XPathPathExpr ], 20,
1303             \&passExpr ],
1304             [ $XPathUnionExpr, [ $XPathUnionExpr, $TOK_PIPE, $XPathPathExpr ], 20,
1305             \&makeUnionExpr ],
1306              
1307             [ $XPathPathExpr, [ $XPathLocationPath ], 20,
1308             \&passExpr ],
1309             [ $XPathPathExpr, [ $XPathFilterExpr ], 19,
1310             \&passExpr ],
1311             [ $XPathPathExpr,
1312             [ $XPathFilterExpr, $TOK_SLASH, $XPathRelativeLocationPath ], 20,
1313             \&makePathExpr1 ],
1314             [ $XPathPathExpr,
1315             [ $XPathFilterExpr, $TOK_DSLASH, $XPathRelativeLocationPath ], 20,
1316             \&makePathExpr2 ],
1317              
1318             [ $XPathFilterExpr, [ $XPathPrimaryExpr, $XPathPredicate, $Q_MM ], 20,
1319             \&makeFilterExpr ],
1320              
1321             [ $XPathExpr, [ $XPathPrimaryExpr ], 16,
1322             \&passExpr ],
1323             [ $XPathExpr, [ $XPathUnionExpr ], 16,
1324             \&passExpr ],
1325              
1326             [ $XPathExpr, [ $TOK_MINUS, $XPathExpr ], -1,
1327             \&makeUnaryMinusExpr ],
1328              
1329             [ $XPathExpr, [ $XPathExpr, $TOK_OR, $XPathExpr ], -1,
1330             \&makeBinaryExpr ],
1331             [ $XPathExpr, [ $XPathExpr, $TOK_AND, $XPathExpr ], -1,
1332             \&makeBinaryExpr ],
1333              
1334             [ $XPathExpr, [ $XPathExpr, $TOK_EQ, $XPathExpr ], -1,
1335             \&makeBinaryExpr ],
1336             [ $XPathExpr, [ $XPathExpr, $TOK_NEQ, $XPathExpr ], -1,
1337             \&makeBinaryExpr ],
1338              
1339             [ $XPathExpr, [ $XPathExpr, $TOK_LT, $XPathExpr ], -1,
1340             \&makeBinaryExpr ],
1341             [ $XPathExpr, [ $XPathExpr, $TOK_LE, $XPathExpr ], -1,
1342             \&makeBinaryExpr ],
1343             [ $XPathExpr, [ $XPathExpr, $TOK_GT, $XPathExpr ], -1,
1344             \&makeBinaryExpr ],
1345             [ $XPathExpr, [ $XPathExpr, $TOK_GE, $XPathExpr ], -1,
1346             \&makeBinaryExpr ],
1347              
1348             [ $XPathExpr, [ $XPathExpr, $TOK_PLUS, $XPathExpr ], -1,
1349             \&makeBinaryExpr, $ASSOC_LEFT ],
1350             [ $XPathExpr, [ $XPathExpr, $TOK_MINUS, $XPathExpr ], -1,
1351             \&makeBinaryExpr, $ASSOC_LEFT ],
1352              
1353             [ $XPathExpr, [ $XPathExpr, $TOK_ASTERISK, $XPathExpr ], -1,
1354             \&makeBinaryExpr, $ASSOC_LEFT ],
1355             [ $XPathExpr, [ $XPathExpr, $TOK_DIV, $XPathExpr ], -1,
1356             \&makeBinaryExpr, $ASSOC_LEFT ],
1357             [ $XPathExpr, [ $XPathExpr, $TOK_MOD, $XPathExpr ], -1,
1358             \&makeBinaryExpr, $ASSOC_LEFT ],
1359              
1360             [ $XPathLiteral, [ $TOK_LITERALQ ], -1,
1361             \&makeLiteralExpr ],
1362             [ $XPathLiteral, [ $TOK_LITERALQQ ], -1,
1363             \&makeLiteralExpr ],
1364              
1365             [ $XPathNumber, [ $TOK_NUMBER ], -1,
1366             \&makeNumberExpr ],
1367              
1368             [ $XPathVariableReference, [ $TOK_DOLLAR, $TOK_QNAME ], 200,
1369             \&makeVariableReference ]
1370             ];
1371              
1372             our $xpathRules = [];
1373              
1374 1     1 0 307 sub new { bless { }, $_[0] }
1375              
1376             sub createContext {
1377 2     2 0 3 my $self = shift;
1378 2         14 return XML::DOM::Lite::XPath::ExprContext->new(@_);
1379             }
1380              
1381             sub evaluate {
1382 2     2 0 8 my ($self, $expr, $ctx) = @_;
1383 2 50       17 if ($ctx->nodeType) {
1384 2         8 $ctx = $self->createContext($ctx);
1385             }
1386 2         7 return $self->parse($expr)->evaluate($ctx)->{value};
1387             }
1388              
1389             our $PARSE_CACHE = { };
1390             sub parse {
1391 9     9 0 19 my ($self, $expr) = @_;
1392 9 50       27 $DEBUG && warn('XPath parse ' . $expr);
1393 9         23 xpathParseInit();
1394              
1395 9         20 my $cached = cacheLookup($expr);
1396 9 100       21 if ($cached) {
1397 4 50       9 $DEBUG && warn(' ... cached');
1398 4         9 return $cached;
1399             }
1400 5 50       43 if ($expr =~ /^(\$|@)?\w+$/i) {
1401 0         0 my $ret = makeSimpleExpr($expr);
1402 0         0 $PARSE_CACHE->{$expr} = $ret;
1403 0 0       0 $DEBUG && warn(' ... simple');
1404 0         0 return $ret;
1405             }
1406              
1407 5 100       22 if ($expr =~ /^\w+(\/\w+)*$/i) {
1408 1         7 my $ret = makeSimpleExpr2($expr);
1409 1         3 $PARSE_CACHE->{$expr} = $ret;
1410 1 50       3 $DEBUG && warn(' ... simple2');
1411 1         3 return $ret;
1412             }
1413              
1414 4         8 my $cachekey = $expr;
1415 4         7 my $stack = [];
1416 4         9 my $ahead = undef;
1417 4         6 my $previous = undef;
1418 4         8 my $done = 0;
1419              
1420 4         7 my $parse_count = 0;
1421 4         6 my $lexer_count = 0;
1422 4         7 my $reduce_count = 0;
1423            
1424 4         12 until ($done) {
1425 22         33 $parse_count++;
1426 22         96 $expr =~ s/^\s*//;
1427 22         39 $previous = $ahead;
1428 22         33 $ahead = undef;
1429              
1430 22         29 my $rule = undef;
1431 22         30 my $match = '';
1432 22         45 foreach my $r (@$xpathTokenRules) {
1433 439         642 my $re = $r->{re};
1434 439         4835 my @result = ($expr =~ /($re)/);
1435 439         678 $lexer_count++;
1436 439 100 66     1098 if (@result and length($result[0]) > length($match)) {
1437 18         24 $rule = $r;
1438 18         27 $match = $result[0];
1439 18         38 last;
1440             }
1441             }
1442              
1443 22 0 33     184 if ($rule &&
      66        
      0        
      33        
1444             ($rule == $TOK_DIV ||
1445             $rule == $TOK_MOD ||
1446             $rule == $TOK_AND ||
1447             $rule == $TOK_OR) &&
1448             (!$previous ||
1449             $previous->{tag} == $TOK_AT ||
1450             $previous->{tag} == $TOK_DSLASH ||
1451             $previous->{tag} == $TOK_SLASH ||
1452             $previous->{tag} == $TOK_AXIS ||
1453             $previous->{tag} == $TOK_DOLLAR)) {
1454 0         0 $rule = $TOK_QNAME;
1455             }
1456              
1457 22 100       48 if ($rule) {
1458 18         51 $expr = substr($expr, length($match));
1459 18 50       45 $DEBUG && warn('token: ' . $match . ' -- ' . $rule->{label});
1460             $ahead = {
1461             tag => $rule,
1462             match => $match,
1463 18 100       68 prec => $rule->{prec} ? $rule->{prec} : 0,
1464             expr => makeTokenExpr($match)
1465             };
1466              
1467             } else {
1468 4 50       16 $DEBUG && warn "DONE";
1469 4         6 $done = 1;
1470             }
1471              
1472 22         54 while (reduce($stack, $ahead)) {
1473 59         77 $reduce_count++;
1474 59 50       145 $DEBUG && warn ('stack: ' . stackToString($stack));
1475             }
1476             }
1477              
1478 4 50       12 $DEBUG && warn(stackToString($stack));
1479              
1480 4 50       14 if (@$stack != 1) {
1481 0         0 die 'XPath parse error ' . $cachekey . ":\n" . stackToString($stack);
1482             }
1483              
1484 4         9 my $result = $stack->[0]->{expr};
1485 4         9 $PARSE_CACHE->{$cachekey} = $result;
1486              
1487 4 50       15 $DEBUG && warn('XPath parse: '.$parse_count.' / '.$lexer_count.' / '.$reduce_count);
1488              
1489 4         70 return $result;
1490             }
1491              
1492             sub cacheLookup {
1493 9     9 0 20 my ($expr) = @_;
1494 9         15 return $PARSE_CACHE->{$expr};
1495             }
1496              
1497             sub reduce {
1498 81     81 0 116 my ($stack, $ahead) = @_;
1499 81         101 my $cand = undef;
1500              
1501 81 100       159 if (@$stack) {
1502 77         104 my $top = $stack->[@$stack-1];
1503 77         144 my $ruleset = $xpathRules->[$top->{tag}->{key}];
1504 77 100       140 if ($ruleset) {
1505 71         113 foreach my $rule (@$ruleset) {
1506 238         388 my $match = matchStack($stack, $rule->[1]);
1507 238 100       459 if (@$match) {
1508 63         145 $cand = {
1509             tag => $rule->[0],
1510             rule => $rule,
1511             match => $match
1512             };
1513 63         104 $cand->{prec} = grammarPrecedence($cand);
1514 63         106 last;
1515             }
1516             }
1517             }
1518             }
1519              
1520 81         98 my $ret;
1521 81 100 66     338 if ($cand and ((not $ahead) or ($cand->{prec} > $ahead->{prec}) or
      66        
1522             ($ahead->{tag}->{left} and $cand->{prec} >= $ahead->{prec}))) {
1523 59         419 for (my $i = 0; $i < $cand->{match}->{matchlength}; ++$i) {
1524 73         157 pop(@$stack);
1525             }
1526              
1527             $DEBUG && warn('reduce '. $cand->{tag}->{label}.' '
1528             .$cand->{prec}.' ahead '.(
1529             $ahead ? $ahead->{tag}->{label}.
1530             ' '.$ahead->{prec}.($ahead->{tag}->{left}
1531 59 0       126 ? ' left' : '')
    0          
    50          
1532             : ' none ')
1533             );
1534 59         74 my $matchexpr = [ map { $_->{expr} } @{$cand->{match}} ];
  75         153  
  59         99  
1535 59         139 $cand->{expr} = $cand->{rule}->[3]->(@$matchexpr);
1536              
1537 59         83 push @$stack, $cand;
1538 59         84 $ret = 1;
1539              
1540             } else {
1541 22 100       42 if ($ahead) {
1542             $DEBUG && warn('shift '.$ahead->{tag}->{label}.' '.
1543             $ahead->{prec}.($ahead->{tag}->{left} ? ' left' : '').
1544             ' over '.($cand ? $cand->{tag}->{label}.' '
1545 18 0       40 .$cand->{prec} : ' none'));
    0          
    50          
1546 18         29 push @$stack, $ahead;
1547             }
1548 22         33 $ret = 0;
1549             }
1550 81         198 return $ret;
1551             }
1552              
1553             sub matchStack {
1554 238     238 0 340 my ($stack, $pattern) = @_;
1555              
1556 238         274 my $S = @$stack;
1557 238         279 my $P = @$pattern;
1558 238         265 my ($p, $s);
1559 238         419 my $match = Array::Object->new([]);
1560 238         391 $match->{matchlength} = 0;
1561 238         277 my $ds = 0;
1562 238   100     825 for ($p = $P - 1, $s = $S - 1; $p >= 0 && $s >= 0; --$p, $s -= $ds) {
1563 359         456 $ds = 0;
1564 359         581 my $qmatch = Array::Object->new([]);
1565 359 100       1096 if ($pattern->[$p] == $Q_MM) {
    50          
    50          
    100          
1566 4         7 $p -= 1;
1567 4         7 push @$match, $qmatch;
1568 4   66     24 while ($s - $ds >= 0 and $stack->[$s - $ds]->{tag} == $pattern->[$p]) {
1569 2         5 push(@$qmatch, $stack->[$s - $ds]);
1570 2         5 $ds += 1;
1571 2         9 $match->{matchlength} += 1;
1572             }
1573              
1574             } elsif ($pattern->[$p] == $Q_01) {
1575 0         0 $p -= 1;
1576 0         0 push(@$match, $qmatch);
1577 0   0     0 while ($s - $ds >= 0 and $ds < 2 and $stack->[$s - $ds]->{tag} == $pattern->[$p]) {
      0        
1578 0         0 push(@$qmatch, $stack->[$s - $ds]);
1579 0         0 $ds += 1;
1580 0         0 $match->{matchlength} += 1;
1581             }
1582              
1583             } elsif ($pattern->[$p] == $Q_1M) {
1584 0         0 $p -= 1;
1585 0         0 push(@$match, $qmatch);
1586 0 0       0 if ($stack->[$s]->{tag} == $pattern->[$p]) {
1587 0   0     0 while ($s - $ds >= 0 and $stack->[$s - $ds]->{tag} == $pattern->[$p]) {
1588 0         0 push(@$qmatch, $stack->[$s - $ds]);
1589 0         0 $ds += 1;
1590 0         0 $match->{matchlength} += 1;
1591             }
1592             } else {
1593 0         0 return [];
1594             }
1595              
1596             } elsif ($stack->[$s]->{tag} == $pattern->[$p]) {
1597 256         387 push(@$match, $stack->[$s]);
1598 256         328 $ds += 1;
1599 256         321 $match->{matchlength} += 1;
1600              
1601             } else {
1602 99         242 return [];
1603             }
1604              
1605 260         358 @$qmatch = reverse(@$qmatch);
1606 260         331 $qmatch->{expr} = [ map { $_->{expr} } @$qmatch ];
  2         11  
1607             }
1608              
1609 139         218 @$match = reverse(@$match);
1610              
1611 139 100       231 if ($p == -1) {
1612 63         117 return $match;
1613              
1614             } else {
1615 76         154 return [];
1616             }
1617             }
1618              
1619             sub tokenPrecedence {
1620 8     8 0 13 my ($tag) = @_;
1621 8   100     30 return $tag->{prec} || 2;
1622             }
1623              
1624             sub grammarPrecedence {
1625 63     63 0 96 my ($frame) = @_;
1626 63         72 my $ret = 0;
1627              
1628 63 50 0     104 if ($frame->{rule}) {
    0          
    0          
1629 63 100 66     74 if (@{$frame->{rule}} >= 3 and $frame->{rule}->[2] >= 0) {
  63         237  
1630 59         85 $ret = $frame->{rule}->[2];
1631              
1632             } else {
1633 4         9 for (my $i = 0; $i < @{$frame->{rule}->[1]}; ++$i) {
  12         32  
1634 8         18 my $p = tokenPrecedence($frame->{rule}->[1]->[$i]);
1635 8         17 $ret = max($ret, $p);
1636             }
1637             }
1638             } elsif ($frame->{tag}) {
1639 0         0 $ret = tokenPrecedence($frame->{tag});
1640              
1641             } elsif (ref $frame eq 'ARRAY' and @$frame) {
1642 0         0 for (my $j = 0; $j < @$frame; ++$j) {
1643 0         0 my $p = grammarPrecedence($frame->[$j]);
1644 0         0 $ret = max($ret, $p);
1645             }
1646             }
1647              
1648 63         91 return $ret;
1649             }
1650              
1651 8 100   8 0 18 sub max { if ($_[0] > $_[1]) { return $_[0] } else { return $_[1] } }
  2         5  
  6         12  
1652              
1653             sub stackToString {
1654 0     0 0 0 my $stack = shift;
1655 0         0 my $ret = '';
1656 0         0 for (my $i = 0; $i < @$stack; ++$i) {
1657 0 0       0 if ($ret) {
1658 0         0 $ret .= "\n";
1659             }
1660 0         0 $ret .= $stack->[$i]->{tag}->{label};
1661             }
1662 0         0 return $ret;
1663             }
1664             sub makeTokenExpr {
1665 18     18 0 37 my ($m) = @_;
1666 18         57 return XML::DOM::Lite::XPath::TokenExpr->new($m);
1667             }
1668              
1669             sub passExpr {
1670 32     32 0 50 my ($e) = shift;
1671 32         49 return $e;
1672             }
1673              
1674             sub makeLocationExpr1 {
1675 1     1 0 4 my ($slash, $rel) = @_;
1676 1         2 $rel->{absolute} = 1;
1677 1         2 return $rel;
1678             }
1679              
1680             sub makeLocationExpr2 {
1681 1     1 0 2 my ($dslash, $rel) = @_;
1682 1         3 $rel->{absolute} = 1;
1683 1         3 $rel->prependStep(makeAbbrevStep($dslash->{value}));
1684 1         2 return $rel;
1685             }
1686              
1687             sub makeLocationExpr3 {
1688 1     1 0 2 my $slash = shift;
1689 1         7 my $ret = XML::DOM::Lite::XPath::LocationExpr->new();
1690 1         4 $ret->appendStep(makeAbbrevStep('.'));
1691 1         2 $ret->{absolute} = 1;
1692 1         3 return $ret;
1693             }
1694              
1695             sub makeLocationExpr4 {
1696 0     0 0 0 my $dslash = shift;
1697 0         0 my $ret = XML::DOM::Lite::XPath::LocationExpr->new();
1698 0         0 $ret->{absolute} = 1;
1699 0         0 $ret->appendStep(makeAbbrevStep($dslash->{value}));
1700 0         0 return $ret;
1701             }
1702              
1703             sub makeLocationExpr5 {
1704 5     5 0 10 my $step = shift;
1705 5         24 my $ret = XML::DOM::Lite::XPath::LocationExpr->new();
1706 5         15 $ret->appendStep($step);
1707 5         9 return $ret;
1708             }
1709              
1710             sub makeLocationExpr6 {
1711 0     0 0 0 my ($rel, $slash, $step) = @_;
1712 0         0 $rel->appendStep($step);
1713 0         0 return $rel;
1714             }
1715              
1716             sub makeLocationExpr7 {
1717 0     0 0 0 my ($rel, $dslash, $step) = @_;
1718 0         0 $rel->appendStep(makeAbbrevStep($dslash->{value}));
1719 0         0 return $rel;
1720             }
1721              
1722             sub makeStepExpr1 {
1723 1     1 0 3 my $dot = shift;
1724 1         5 return makeAbbrevStep($dot->{value});
1725             }
1726              
1727             sub makeStepExpr2 {
1728 0     0 0 0 my ($ddot) = shift;
1729 0         0 return makeAbbrevStep($ddot->{value});
1730             }
1731              
1732             sub makeStepExpr3 {
1733 0     0 0 0 my ($axisname, $axis, $nodetest) = @_;
1734 0         0 return XML::DOM::Lite::XPath::StepExpr->new($axisname->{value}, $nodetest);
1735             }
1736              
1737             sub makeStepExpr4 {
1738 2     2 0 6 my ($at, $nodetest) = @_;
1739 2         7 return XML::DOM::Lite::XPath::StepExpr->new('attribute', $nodetest);
1740             }
1741              
1742             sub makeStepExpr5 {
1743 2     2 0 4 my $nodetest = shift;
1744 2         15 return XML::DOM::Lite::XPath::StepExpr->new('child', $nodetest);
1745             }
1746              
1747             sub makeStepExpr6 {
1748 2     2 0 5 my ($step, $predicate) = @_;
1749 2         9 $step->appendPredicate($predicate);
1750 2         4 return $step;
1751             }
1752              
1753             sub makeAbbrevStep {
1754 3     3 0 7 my ($abbrev) = @_;
1755 3 100       10 if ($abbrev eq '//') {
    50          
    0          
1756 1         6 return XML::DOM::Lite::XPath::StepExpr->new('descendant-or-self', XML::DOM::Lite::XPath::NodeTestAny->new());
1757             } elsif ($abbrev eq '.') {
1758 2         10 return XML::DOM::Lite::XPath::StepExpr->new('self', XML::DOM::Lite::XPath::NodeTestAny->new());
1759             } elsif ($abbrev eq '..') {
1760 0         0 return XML::DOM::Lite::XPath::StepExpr->new('parent', XML::DOM::Lite::XPath::NodeTestAny->new());
1761             }
1762             }
1763              
1764             sub makeNodeTestExpr1 {
1765 0     0 0 0 my ($asterisk) = @_;
1766 0         0 return XML::DOM::Lite::XPath::NodeTestElement->new();
1767             }
1768              
1769             sub makeNodeTestExpr2 {
1770 0     0 0 0 my ($ncname, $colon, $asterisk) = @_;
1771 0         0 return XML::DOM::Lite::XPath::NodeTestNC->new($ncname->{value});
1772             }
1773              
1774             sub makeNodeTestExpr3 {
1775 4     4 0 6 my $qname = shift;
1776 4         27 return XML::DOM::Lite::XPath::NodeTestName->new($qname->{value});
1777             }
1778              
1779             sub makeNodeTestExpr4 {
1780 0     0 0 0 my ($type, $parenc) = @_;
1781 0         0 $type =~ s/\s*\($//;
1782 0 0       0 if ($type eq 'node') {
    0          
    0          
    0          
1783 0         0 return XML::DOM::Lite::XPath::NodeTestAny->new();
1784             } elsif ($type eq 'text') {
1785 0         0 return XML::DOM::Lite::XPath::NodeTestText->new();
1786             } elsif ($type eq 'comment') {
1787 0         0 return XML::DOM::Lite::XPath::NodeTestComment->new();
1788             } elsif ($type eq 'processing-instruction') {
1789 0         0 return XML::DOM::Lite::XPath::NodeTestPI->new;
1790             }
1791             }
1792              
1793             sub makeNodeTestExpr5 {
1794 0     0 0 0 my ($type, $target, $parenc) = @_;
1795 0         0 $type =~ s/\s*\($//;
1796 0 0       0 if ($type ne 'processing-instruction') {
1797 0         0 die $type.' ';
1798             }
1799 0         0 return XML::DOM::Lite::XPath::NodeTestPI->new($target->{value});
1800             }
1801              
1802             sub makePredicateExpr {
1803 2     2 0 6 my ($pareno, $expr, $parenc) = @_;
1804 2         14 return XML::DOM::Lite::XPath::PredicateExpr->new($expr);
1805             }
1806              
1807             sub makePrimaryExpr {
1808 0     0 0 0 my ($pareno, $expr, $parenc) = @_;
1809 0         0 return $expr;
1810             }
1811              
1812             sub makeFunctionCallExpr1 {
1813 0     0 0 0 my ($name, $pareno, $parenc) = @_;
1814 0         0 return XML::DOM::Lite::XPath::FunctionCallExpr->new($name);
1815             }
1816              
1817             sub makeFunctionCallExpr2 {
1818 0     0 0 0 my ($name, $pareno, $arg1, $args, $parenc) = @_;
1819 0         0 my $ret = XML::DOM::Lite::XPath::FunctionCallExpr->new($name);
1820 0         0 $ret->appendArg($arg1);
1821 0         0 for (my $i = 0; $i < @$args; ++$i) {
1822 0         0 $ret->appendArg($args->[$i]);
1823             }
1824 0         0 return $ret;
1825             }
1826              
1827             sub makeArgumentExpr {
1828 0     0 0 0 my ($comma, $expr) = @_;
1829 0         0 return $expr;
1830             }
1831              
1832             sub makeUnionExpr {
1833 0     0 0 0 my ($expr1, $pipe, $expr2) = @_;
1834 0         0 return XML::DOM::Lite::XPath::UnionExpr->new($expr1, $expr2);
1835             }
1836              
1837             sub makePathExpr1 {
1838 0     0 0 0 my ($filter, $slash, $rel) = @_;
1839 0         0 return XML::DOM::Lite::XPath::PathExpr->new($filter, $rel);
1840             }
1841              
1842             sub makePathExpr2 {
1843 0     0 0 0 my ($filter, $dslash, $rel) = @_;
1844 0         0 $rel->prependStep(makeAbbrevStep($dslash->{value}));
1845 0         0 return XML::DOM::Lite::XPath::PathExpr->new($filter, $rel);
1846             }
1847              
1848             sub makeFilterExpr {
1849 2     2 0 5 my ($expr, $predicates) = @_;
1850 2 50       9 if (@$predicates > 0) {
1851 0         0 return XML::DOM::Lite::XPath::FilterExpr->new($expr, $predicates);
1852             } else {
1853 2         5 return $expr;
1854             }
1855             }
1856              
1857             sub makeUnaryMinusExpr {
1858 0     0 0 0 my ($minus, $expr) = @_;
1859 0         0 return new XML::DOM::Lite::XPath::UnaryMinusExpr($expr);
1860             }
1861              
1862             sub makeBinaryExpr {
1863 2     2 0 8 my ($expr1, $op, $expr2) = @_;
1864 2         17 return new XML::DOM::Lite::XPath::BinaryExpr($expr1, $op, $expr2);
1865             }
1866              
1867             sub makeLiteralExpr {
1868 2     2 0 5 my ($token) = @_;
1869 2         7 my $value = substr($token->{value}, 1, length($token->{value}) - 1);
1870 2         18 return new XML::DOM::Lite::XPath::LiteralExpr($value);
1871             }
1872              
1873             sub makeNumberExpr {
1874 0     0 0 0 my $token = shift;
1875 0         0 return new XML::DOM::Lite::XPath::NumberExpr($token->{value});
1876             }
1877              
1878             sub makeVariableReference {
1879 0     0 0 0 my ($dollar, $name) = @_;
1880 0         0 return new XML::DOM::Lite::XPath::VariableExpr($name->{value});
1881             }
1882              
1883             sub makeSimpleExpr {
1884 0     0 0 0 my $expr = shift;
1885 0 0       0 if (substr($expr, 0, 1) eq '$') {
    0          
    0          
1886 0         0 return new XML::DOM::Lite::XPath::VariableExpr(substr($expr, 1));
1887             } elsif (substr($expr, 0, 1) eq '@') {
1888 0         0 my $a = new XML::DOM::Lite::XPath::NodeTestName(substr($expr, 1));
1889 0         0 my $b = new XML::DOM::Lite::XPath::StepExpr('attribute', $a);
1890 0         0 my $c = new XML::DOM::Lite::XPath::LocationExpr();
1891 0         0 $c->appendStep($b);
1892 0         0 return $c;
1893             } elsif ($expr =~ /^[0-9]+$/) {
1894 0         0 return new XML::DOM::Lite::XPath::NumberExpr($expr);
1895             } else {
1896 0         0 my $a = new XML::DOM::Lite::XPath::NodeTestName($expr);
1897 0         0 my $b = new XML::DOM::Lite::XPath::StepExpr('child', $a);
1898 0         0 my $c = new XML::DOM::Lite::XPath::LocationExpr();
1899 0         0 $c->appendStep($b);
1900 0         0 return $c;
1901             }
1902             }
1903              
1904             sub makeSimpleExpr2 {
1905 1     1 0 2 my $expr = shift;
1906 1         3 my @steps = split(/\//, $expr);
1907 1         4 my $c = new XML::DOM::Lite::XPath::LocationExpr();
1908 1         2 foreach my $s (@steps) {
1909 2         8 my $a = new XML::DOM::Lite::XPath::NodeTestName($s);
1910 2         7 my $b = new XML::DOM::Lite::XPath::StepExpr('child', $a);
1911 2         4 $c->appendStep($b);
1912             }
1913 1         3 return $c;
1914             }
1915              
1916             sub xpathParseInit {
1917 9 100   9 0 27 if (@$xpathRules) {
1918 6         10 return;
1919             }
1920             @$xpathGrammarRules = sort {
1921 3         19 return scalar(@{$b->[1]}) <=> scalar(@{$a->[1]});
  687         747  
  687         782  
  687         823  
1922             } @$xpathGrammarRules;
1923            
1924 3         9 my $k = 1;
1925 3         14 for (my $i = 0; $i < @$xpathNonTerminals; ++$i) {
1926 51         105 $xpathNonTerminals->[$i]->{key} = $k++;
1927             }
1928              
1929 3         12 for (my $i = 0; $i < @$xpathTokenRules; ++$i) {
1930 102         200 $xpathTokenRules->[$i]->{key} = $k++;
1931             }
1932              
1933 3 50       8 $DEBUG && warn('XPath parse INIT: ' . $k . ' rules');
1934              
1935             my $push_ = sub {
1936 171     171   236 my ($array, $position, $element) = @_;
1937 171 100       338 $array->[$position] = [ ] unless $array->[$position];
1938 171         197 push @{$array->[$position]}, $element;
  171         312  
1939 3         22 };
1940              
1941 3         10 for (my $i = 0; $i < @$xpathGrammarRules; ++$i) {
1942 168         207 my $rule = $xpathGrammarRules->[$i];
1943 168         227 my $pattern = $rule->[1];
1944              
1945 168         316 for (my $j = @$pattern - 1; $j >= 0; --$j) {
1946 171 50 66     615 if ($pattern->[$j] == $Q_1M) {
    100          
1947 0         0 &$push_($xpathRules, $pattern->[$j-1]->{key}, $rule);
1948 0         0 last;
1949            
1950             } elsif ($pattern->[$j] == $Q_MM or $pattern->[$j] == $Q_01) {
1951 3         14 &$push_($xpathRules, $pattern->[$j-1]->{key}, $rule);
1952 3         8 --$j;
1953              
1954             } else {
1955 168         321 &$push_($xpathRules, $pattern->[$j]->{key}, $rule);
1956 168         380 last;
1957             }
1958             }
1959             }
1960              
1961 3 50       11 $DEBUG && warn('XPath parse INIT: ' . @$xpathRules . ' rule bins');
1962            
1963 3         5 my $sum = 0;
1964 3 100       9 map { if ($_) { $sum += @$_} } @$xpathRules;
  150         260  
  78         161  
1965            
1966 3 50       19 $DEBUG && warn('XPath parse INIT: ' . ($sum / @$xpathRules) . ' average bin size');
1967             }
1968              
1969             sub xpathCollectDescendants {
1970 4     4 0 5 my ($nodelist, $node) = @_;
1971 4         12 for (my $n = $node->firstChild; $n; $n = $n->nextSibling) {
1972 3         5 push(@$nodelist, $n);
1973 3         9 xpathCollectDescendants($nodelist, $n);
1974             }
1975             }
1976              
1977             sub xpathCollectDescendantsReverse {
1978 0     0 0 0 my ($nodelist, $node) = @_;
1979 0         0 for (my $n = $node->lastChild; $n; $n = $n->previousSibling) {
1980 0         0 push(@$nodelist, $n);
1981 0         0 xpathCollectDescendantsReverse($nodelist, $n);
1982             }
1983             }
1984              
1985              
1986             sub xpathDomEval {
1987 0     0 0 0 my ($expr, $node) = @_;
1988 0         0 my $expr1 = xpathParse($expr);
1989 0         0 my $ret = $expr1->evaluate(XML::DOM::Lite::XPath::ExprContext($node)->new);
1990 0         0 return $ret;
1991             }
1992              
1993             sub xpathSort {
1994 2     2 0 4 my ($input, $sort) = @_;
1995 2 50       11 return unless @$sort;
1996              
1997 0         0 my $sortlist = [];
1998              
1999 0         0 for (my $i = 0; $i < @{$input->{nodelist}}; ++$i) {
  0         0  
2000 0         0 my $node = $input->{nodelist}->[$i];
2001 0         0 my $sortitem = { node=> $node, key=> [] };
2002 0         0 my $context = $input->clone($node, 0, [ $node ]);
2003            
2004 0         0 for (my $j = 0; $j < @$sort; ++$j) {
2005 0         0 my $s = $sort->[$j];
2006 0         0 my $value = $s->{expr}->evaluate($context);
2007              
2008 0         0 my $evalue;
2009 0 0       0 if ($s->{type} eq 'text') {
    0          
2010 0         0 $evalue = $value->stringValue();
2011             } elsif ($s->{type} eq 'number') {
2012 0         0 $evalue = $value->numberValue();
2013             }
2014 0         0 push @{$sortitem->{key}}, { value=> $evalue, order=> $s->{order} };
  0         0  
2015             }
2016              
2017 0         0 push @{$sortitem->{key}}, {value => $i, order => 'ascending'};
  0         0  
2018              
2019 0         0 push @$sortlist, $sortitem;
2020             }
2021              
2022 0         0 @$sortlist = sort \&xpathSortByKey, @$sortlist;
2023              
2024 0         0 my $nodes = [];
2025 0         0 for (my $i = 0; $i < @$sortlist; ++$i) {
2026 0         0 push(@$nodes, $sortlist->[$i]->{node});
2027             }
2028 0         0 $input->{nodelist} = $nodes;
2029 0         0 $input->setNode($nodes->[0], 0);
2030             }
2031              
2032             sub xpathSortByKey {
2033 0     0 0 0 my ($v1, $v2) = @_;
2034 0         0 for (my $i = 0; $i < @{$v1->{key}}; ++$i) {
  0         0  
2035 0 0       0 my $o = $v1->{key}->[$i]->{order} eq 'descending' ? -1 : 1;
2036 0 0       0 if ($v1->{key}->[$i]->{value} > $v2->{key}->[$i]->{value}) {
    0          
2037 0         0 return +1 * $o;
2038             } elsif ($v1->{key}->[$i]->{value} < $v2->{key}->[$i]->{value}) {
2039 0         0 return -1 * $o;
2040             }
2041             }
2042              
2043 0         0 return 0;
2044             }
2045              
2046             sub xmlValue {
2047 4     4 0 8 my $node = shift;
2048 4 50       13 return '' unless $node;
2049              
2050 4         11 my $ret = '';
2051 4 100 66     38 if ($node->{nodeType} == TEXT_NODE ||
    50 100        
      33        
      33        
2052             $node->{nodeType} == CDATA_SECTION_NODE ||
2053             $node->{nodeType} == ATTRIBUTE_NODE) {
2054 3         11 $ret .= $node->{nodeValue};
2055              
2056             } elsif ($node->{nodeType} == ELEMENT_NODE ||
2057             $node->{nodeType} == DOCUMENT_NODE ||
2058             $node->{nodeType} == DOCUMENT_FRAGMENT_NODE) {
2059 1         2 for (my $i = 0; $i < @{$node->childNodes}; ++$i) {
  2         6  
2060 1         3 $ret .= xmlValue($node->childNodes->[$i]);
2061             }
2062             }
2063 4         9 return $ret;
2064             }
2065              
2066             1;
2067              
2068             __END__