File Coverage

blib/lib/XML/XPathEngine/Step.pm
Criterion Covered Total %
statement 146 262 55.7
branch 51 150 34.0
condition 18 52 34.6
subroutine 19 30 63.3
pod 0 22 0.0
total 234 516 45.3


line stmt bran cond sub pod time code
1             # $Id: Step.pm,v 1.35 2001/04/01 16:56:40 matt Exp $
2              
3             package XML::XPathEngine::Step;
4 2     2   11 use XML::XPathEngine;
  2         2  
  2         57  
5 2     2   11 use strict;
  2         2  
  2         2140  
6              
7             # the beginnings of using XS for this file...
8             # require DynaLoader;
9             # use vars qw/$VERSION @ISA/;
10             # $VERSION = '1.0';
11             # @ISA = qw(DynaLoader);
12             #
13             # bootstrap XML::XPathEngine::Step $VERSION;
14              
15             sub test_qname () { 0; } # Full name
16             sub test_ncwild () { 1; } # NCName:*
17             sub test_any () { 2; } # *
18              
19             sub test_attr_qname () { 3; } # @ns:attrib
20             sub test_attr_ncwild () { 4; } # @nc:*
21             sub test_attr_any () { 5; } # @*
22              
23             sub test_nt_comment () { 6; } # comment()
24             sub test_nt_text () { 7; } # text()
25             sub test_nt_pi () { 8; } # processing-instruction()
26             sub test_nt_node () { 9; } # node()
27              
28             sub new {
29 118     118 0 155 my $class = shift;
30 118         404 my ($pp, $axis, $test, $literal) = @_;
31 118         188 my $axis_method = "axis_$axis";
32 118         180 $axis_method =~ tr/-/_/;
33 118         634 my $self = {
34             pp => $pp, # the XML::XPathEngine class
35             axis => $axis,
36             axis_method => $axis_method,
37             test => $test,
38             literal => $literal,
39             predicates => [],
40             };
41 118         666 bless $self, $class;
42             }
43              
44             sub as_string {
45 0     0 0 0 my $self = shift;
46 0         0 my $string = $self->{axis} . "::";
47              
48 0         0 my $test = $self->{test};
49            
50 0 0 0     0 if ($test == test_nt_pi) {
    0          
    0          
    0          
    0          
51 0         0 $string .= 'processing-instruction(';
52 0 0       0 if ($self->{literal}->value) {
53 0         0 $string .= $self->{literal}->as_string;
54             }
55 0         0 $string .= ")";
56             }
57             elsif ($test == test_nt_comment) {
58 0         0 $string .= 'comment()';
59             }
60             elsif ($test == test_nt_text) {
61 0         0 $string .= 'text()';
62             }
63             elsif ($test == test_nt_node) {
64 0         0 $string .= 'node()';
65             }
66             elsif ($test == test_ncwild || $test == test_attr_ncwild) {
67 0         0 $string .= $self->{literal} . ':*';
68             }
69             else {
70 0         0 $string .= $self->{literal};
71             }
72            
73 0         0 foreach (@{$self->{predicates}}) {
  0         0  
74 0 0       0 next unless defined $_;
75 0         0 $string .= "[" . $_->as_string . "]";
76             }
77 0         0 return $string;
78             }
79              
80             sub as_xml {
81 0     0 0 0 my $self = shift;
82 0         0 my $string = "\n";
83 0         0 $string .= "" . $self->{axis} . "\n";
84 0         0 my $test = $self->{test};
85            
86 0         0 $string .= "";
87            
88 0 0 0     0 if ($test == test_nt_pi) {
    0          
    0          
    0          
    0          
89 0         0 $string .= '
90 0 0       0 if ($self->{literal}->value) {
91 0         0 $string .= '>';
92 0         0 $string .= $self->{literal}->as_string;
93 0         0 $string .= '';
94             }
95             else {
96 0         0 $string .= '/>';
97             }
98             }
99             elsif ($test == test_nt_comment) {
100 0         0 $string .= '';
101             }
102             elsif ($test == test_nt_text) {
103 0         0 $string .= '';
104             }
105             elsif ($test == test_nt_node) {
106 0         0 $string .= '';
107             }
108             elsif ($test == test_ncwild || $test == test_attr_ncwild) {
109 0         0 $string .= '' . $self->{literal} . '';
110             }
111             else {
112 0         0 $string .= '' . $self->{literal} . '';
113             }
114            
115 0         0 $string .= "\n";
116            
117 0         0 foreach (@{$self->{predicates}}) {
  0         0  
118 0 0       0 next unless defined $_;
119 0         0 $string .= "\n" . $_->as_xml() . "\n";
120             }
121            
122 0         0 $string .= "\n";
123            
124 0         0 return $string;
125             }
126              
127             sub evaluate {
128 275     275 0 323 my $self = shift;
129 275         307 my $from = shift; # context nodeset
130              
131 275 100 66     728 if( $from && !$from->isa( 'XML::XPathEngine::NodeSet'))
132             {
133 3         11 my $from_nodeset= XML::XPathEngine::NodeSet->new();
134 3         9 $from_nodeset->push( $from);
135 3         6 $from= $from_nodeset;
136             }
137             #warn "Step::evaluate called with ", $from->size, " length nodeset\n";
138            
139 275         993 my $saved_context = $self->{pp}->_get_context_set;
140 275         748 my $saved_pos = $self->{pp}->_get_context_pos;
141 275         668 $self->{pp}->_set_context_set($from);
142            
143 275         680 my $initial_nodeset = XML::XPathEngine::NodeSet->new();
144            
145             # See spec section 2.1, paragraphs 3,4,5:
146             # The node-set selected by the location step is the node-set
147             # that results from generating an initial node set from the
148             # axis and node-test, and then filtering that node-set by
149             # each of the predicates in turn.
150            
151             # Make each node in the nodeset be the context node, one by one
152 275         723 for(my $i = 1; $i <= $from->size; $i++) {
153 618         1744 $self->{pp}->_set_context_pos($i);
154 618         1437 $initial_nodeset->append($self->evaluate_node($from->get_node($i)));
155             }
156            
157             # warn "Step::evaluate initial nodeset size: ", $initial_nodeset->size, "\n";
158            
159 272         775 $self->{pp}->_set_context_set($saved_context);
160 272         703 $self->{pp}->_set_context_pos($saved_pos);
161              
162 272         1067 return $initial_nodeset;
163             }
164              
165             # Evaluate the step against a particular node
166             sub evaluate_node {
167 618     618 0 772 my $self = shift;
168 618         692 my $context = shift;
169            
170             # warn "Evaluate node: $self->{axis}\n";
171            
172             # warn "Node: ", $context->[node_name], "\n";
173            
174 618         922 my $method = $self->{axis_method};
175            
176 618         1804 my $results = XML::XPathEngine::NodeSet->new();
177 2     2   13 no strict 'refs';
  2         4  
  2         5598  
178 618         844 eval {
179 618         1388 $method->($self, $context, $results);
180             };
181 618 100       4000 if ($@) {
182 3         33 die "axis $method not implemented [$@]\n";
183             }
184            
185             # warn("results: ", join('><', map {$_->string_value} @$results), "\n");
186             # filter initial nodeset by each predicate
187 615         778 foreach my $predicate (@{$self->{predicates}}) {
  615         1307  
188 335         683 $results = $self->filter_by_predicate($results, $predicate);
189             }
190            
191 615         2030 return $results;
192             }
193              
194             sub axis_ancestor {
195 0     0 0 0 my $self = shift;
196 0         0 my ($context, $results) = @_;
197            
198 0         0 my $parent = $context->getParentNode;
199            
200 0 0       0 START:
201             return $results unless $parent;
202 0 0       0 if (node_test($self, $parent)) {
203 0         0 $results->push($parent);
204             }
205 0         0 $parent = $parent->getParentNode;
206 0         0 goto START;
207             }
208              
209             sub axis_ancestor_or_self {
210 0     0 0 0 my $self = shift;
211 0         0 my ($context, $results) = @_;
212            
213 0 0       0 START:
214             return $results unless $context;
215 0 0       0 if (node_test($self, $context)) {
216 0         0 $results->push($context);
217             }
218 0         0 $context = $context->getParentNode;
219 0         0 goto START;
220             }
221              
222             sub axis_attribute {
223 176     176 0 207 my $self = shift;
224 176         214 my ($context, $results) = @_;
225            
226 176         187 foreach my $attrib (@{$context->getAttributes}) {
  176         434  
227 407 100       2008 if ($self->test_attribute($attrib)) {
228 178         1429 $results->push($attrib);
229             }
230             }
231             }
232              
233             sub axis_child {
234 328     328 0 396 my $self = shift;
235 328         409 my ($context, $results) = @_;
236            
237 328         346 foreach my $node (@{$context->getChildNodes}) {
  328         695  
238 325 100       4190 if (node_test($self, $node)) {
239 122         1651 $results->push($node);
240             }
241             }
242             }
243              
244             sub axis_descendant {
245 13     13 0 18 my $self = shift;
246 13         19 my ($context, $results) = @_;
247              
248 13         83 my @stack = $context->getChildNodes;
249              
250 13         441 while (@stack) {
251 202         3469 my $node = shift @stack;
252 202 100       353 if (node_test($self, $node)) {
253 56         763 $results->push($node);
254             }
255 202         456 unshift @stack, $node->getChildNodes;
256             }
257             }
258              
259             sub axis_descendant_or_self {
260 22     22 0 34 my $self = shift;
261 22         29 my ($context, $results) = @_;
262            
263 22         48 my @stack = ($context);
264              
265 22         55 while (@stack) {
266 345         5993 my $node = shift @stack;
267 345 50       596 if (node_test($self, $node)) {
268 345         829 $results->push($node);
269             }
270             #warn "node is a ", ref( $node);
271 345         742 unshift @stack, $node->getChildNodes;
272             }
273             }
274              
275             sub axis_following
276 5     5 0 8 { my $self = shift;
277 5         9 my ($context, $results) = @_;
278              
279 5   33     15 my $elt= $context->getNextSibling || _next_sibling_of_an_ancestor_of( $context);
280 5         59 while( $elt)
281 30 100       314 { if (node_test($self, $elt)) { $results->push( $elt); }
  9         112  
282 30   100     73 $elt= $elt->getFirstChild || $elt->getNextSibling || _next_sibling_of_an_ancestor_of( $elt);
283             }
284             }
285              
286             sub _next_sibling_of_an_ancestor_of
287 10     10   207 { my $elt= shift;
288 10   50     27 $elt= $elt->getParentNode || return;
289 10         81 my $next_elt;
290 10         24 while( !($next_elt= $elt->getNextSibling))
291 15         128 { $elt= $elt->getParentNode;
292 15 100 66     195 return unless( $elt && $elt->can( 'getNextSibling'));
293             }
294 5         59 return $next_elt;
295             }
296              
297              
298             sub axis_following_sibling {
299 0     0 0 0 my $self = shift;
300 0         0 my ($context, $results) = @_;
301              
302             #warn "in axis_following_sibling";
303 0         0 while ($context = $context->getNextSibling) {
304 0 0       0 if (node_test($self, $context)) {
305 0         0 $results->push($context);
306             }
307             }
308             }
309              
310             sub axis_namespace {
311 0     0 0 0 my $self = shift;
312 0         0 my ($context, $results) = @_;
313            
314 0 0       0 return $results unless $context->isElementNode;
315 0         0 foreach my $ns (@{$context->getNamespaces}) {
  0         0  
316 0 0       0 if ($self->test_namespace($ns)) {
317 0         0 $results->push($ns);
318             }
319             }
320             }
321              
322             sub axis_parent {
323 39     39 0 43 my $self = shift;
324 39         54 my ($context, $results) = @_;
325            
326 39         91 my $parent = $context->getParentNode;
327 38 50       344 return $results unless $parent;
328 38 50       64 if (node_test($self, $parent)) {
329 38         591 $results->push($parent);
330             }
331             }
332              
333             sub axis_preceding
334 3     3 0 7 { my $self = shift;
335 3         6 my ($context, $results) = @_;
336              
337 3   33     10 my $elt= $context->getPreviousSibling || _previous_sibling_of_an_ancestor_of( $context);
338 3         47 while( $elt)
339 18 100       304 { if (node_test($self, $elt)) { $results->push( $elt); }
  6         73  
340 18   100     44 $elt= $elt->getLastChild || $elt->getPreviousSibling || _previous_sibling_of_an_ancestor_of( $elt);
341             }
342             }
343              
344             sub _previous_sibling_of_an_ancestor_of
345 6     6   121 { my $elt= shift;
346 6   50     38 $elt= $elt->getParentNode || return;
347 6         52 my $next_elt;
348 6         14 while( !($next_elt= $elt->getPreviousSibling))
349 6         74 { $elt= $elt->getParentNode;
350 6 100       73 return unless $elt->getParentNode; # so we don't have to write a getPreviousSibling
351             }
352 3         38 return $next_elt;
353             }
354              
355              
356             sub axis_preceding_sibling {
357 0     0 0 0 my $self = shift;
358 0         0 my ($context, $results) = @_;
359            
360 0         0 while ($context = $context->getPreviousSibling) {
361 0 0       0 if (node_test($self, $context)) {
362 0         0 $results->push($context);
363             }
364             }
365             }
366              
367             sub axis_self {
368 32     32 0 37 my $self = shift;
369 32         44 my ($context, $results) = @_;
370            
371 32 50       54 if (node_test($self, $context)) {
372 32         78 $results->push($context);
373             }
374             }
375            
376             sub node_test {
377 990     990 0 1123 my $self = shift;
378 990         1059 my $node = shift;
379            
380             # if node passes test, return true
381            
382 990         1439 my $test = $self->{test};
383              
384 990 100       2314 return 1 if $test == test_nt_node;
385            
386 613 100       1019 if ($test == test_any) {
387 112 50 33     244 return 1 if $node->isElementNode && defined $node->getName;
388             }
389            
390 501         1100 local $^W;
391              
392 501 50 0     1143 if ($test == test_ncwild) {
    50          
    0          
    0          
    0          
    0          
393 0 0       0 return unless $node->isElementNode;
394 0         0 return _match_ns( $self, $node);
395             }
396             elsif ($test == test_qname) {
397 501 50       1170 return unless $node->isElementNode;
398 501 50 33     3842 if ($self->{literal} =~ /:/ || $self->{pp}->{strict_namespaces}) {
399 0         0 my ($prefix, $name) = _name2prefix_and_local_name( $self->{literal});
400 0 0 0     0 return 1 if( ($name eq $node->getLocalName) && _match_ns( $self, $node));
401             }
402             else {
403 501 100       1089 return 1 if $node->getName eq $self->{literal};
404             }
405             }
406             elsif ($test == test_nt_text) {
407 0 0       0 return 1 if $node->isTextNode;
408             }
409             elsif ($test == test_nt_comment) {
410 0 0       0 return 1 if $node->isCommentNode;
411             }
412             elsif ($test == test_nt_pi && !$self->{literal}) {
413 0 0       0 return 1 if $node->isPINode;
414             }
415             elsif ($test == test_nt_pi) {
416 0 0       0 return unless $node->isPINode;
417 0 0       0 if (my $val = $self->{literal}->value) {
418 0 0       0 return 1 if $node->getTarget eq $val;
419             }
420             else {
421 0         0 return 1;
422             }
423             }
424            
425 382         4402 return; # fallthrough returns false
426             }
427              
428             sub _name2prefix_and_local_name
429 0     0   0 { my $name= shift;
430 0 0       0 return $name =~ /:/ ? split(':', $name, 2) : ( '', $name);
431             }
432             sub _name2prefix
433 0     0   0 { my $name= shift;
434 0 0       0 if( $name=~ m{^(.*?):}) { return $1; } else { return ''; }
  0         0  
  0         0  
435             }
436              
437             sub _match_ns
438 0     0   0 { my( $self, $node)= @_;
439 0         0 my $pp= $self->{pp};
440 0         0 my $prefix= _name2prefix( $self->{literal});
441 0         0 my( $match_ns, $node_ns);
442 0 0 0     0 if( $pp->{uses_namespaces} || $pp->{strict_namespaces})
443 0         0 { $match_ns = $pp->get_namespace($prefix);
444 0 0 0     0 if( $match_ns || $pp->{strict_namespaces})
445 0         0 { $node_ns= $node->getNamespace->getValue; }
446             else
447             { # non-standard behaviour: if the query prefix is not declared
448             # compare the 2 prefixes
449 0         0 $match_ns = $prefix;
450 0         0 $node_ns = _name2prefix( $node->getName);
451             }
452             }
453             else
454 0         0 { $match_ns = $prefix;
455 0         0 $node_ns = _name2prefix( $node->getName);
456             }
457              
458 0         0 return $match_ns eq $node_ns;
459             }
460              
461              
462             sub test_attribute {
463 407     407 0 472 my $self = shift;
464 407         429 my $node = shift;
465            
466 407         574 my $test = $self->{test};
467            
468 407 100 66     1602 return 1 if ($test == test_attr_any) || ($test == test_nt_node);
469            
470 331 50       778 if ($test == test_attr_ncwild) {
    50          
471 0 0       0 return 1 if _match_ns( $self, $node);
472             }
473             elsif ($test == test_attr_qname) {
474 331 50       657 if ($self->{literal} =~ /:/) {
475 0         0 my ($prefix, $name) = _name2prefix_and_local_name( $self->{literal});
476              
477 0 0 0     0 return 1 if ( ($name eq $node->getLocalName) && ( _match_ns( $self, $node)) );
478             }
479             else {
480 331 100       718 return 1 if $node->getName eq $self->{literal};
481             }
482             }
483            
484 229         2632 return; # fallthrough returns false
485             }
486              
487             sub test_namespace {
488 0     0 0 0 my $self = shift;
489 0         0 my $node = shift;
490            
491             # Not sure if this is correct. The spec seems very unclear on what
492             # constitutes a namespace test... bah!
493            
494 0         0 my $test = $self->{test};
495            
496 0 0       0 return 1 if $test == test_any; # True for all nodes of principal type
497            
498 0 0       0 if ($test == test_any) {
    0          
499 0         0 return 1;
500             }
501             elsif ($self->{literal} eq $node->getExpanded) {
502 0         0 return 1;
503             }
504            
505 0         0 return;
506             }
507              
508             sub filter_by_predicate {
509 335     335 0 434 my $self = shift;
510 335         431 my ($nodeset, $predicate) = @_;
511            
512             # See spec section 2.4, paragraphs 2 & 3:
513             # For each node in the node-set to be filtered, the predicate Expr
514             # is evaluated with that node as the context node, with the number
515             # of nodes in the node set as the context size, and with the
516             # proximity position of the node in the node set with respect to
517             # the axis as the context position.
518            
519 335 50       712 if (!ref($nodeset)) { # use ref because nodeset has a bool context
520 0         0 die "No nodeset!!!";
521             }
522            
523             # warn "Filter by predicate: $predicate\n";
524            
525 335         879 my $newset = XML::XPathEngine::NodeSet->new();
526              
527 335         887 for(my $i = 1; $i <= $nodeset->size; $i++) {
528             # set context set each time 'cos a loc-path in the expr could change it
529 143         389 $self->{pp}->_set_context_set($nodeset);
530 143         400 $self->{pp}->_set_context_pos($i);
531 143         347 my $result = $predicate->evaluate($nodeset->get_node($i));
532 143 100       640 if ($result->isa('XML::XPathEngine::Boolean')) {
    100          
533 110 100       266 if ($result->value) {
534 41         102 $newset->push($nodeset->get_node($i));
535             }
536             }
537             elsif ($result->isa('XML::XPathEngine::Number')) {
538 17 100       52 if ($result->value == $i) {
539 14         42 $newset->push($nodeset->get_node($i)); last;
  14         26  
540             }
541             }
542             else {
543 16 100       37 if ($result->to_boolean->value) {
544 10         23 $newset->push($nodeset->get_node($i));
545             }
546             }
547             }
548            
549 335         1054 return $newset;
550             }
551              
552             1;