File Coverage

lib/XML/DOM/Lite/XPath.pm
Criterion Covered Total %
statement 377 940 40.1
branch 115 290 39.6
condition 32 102 31.3
subroutine 72 178 40.4
pod 0 55 0.0
total 596 1565 38.0


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