File Coverage

blib/lib/Tree/XPathEngine/Step.pm
Criterion Covered Total %
statement 145 160 90.6
branch 47 68 69.1
condition 7 12 58.3
subroutine 21 22 95.4
pod 19 19 100.0
total 239 281 85.0


line stmt bran cond sub pod time code
1             # $id$
2             # $Id: /tree-xpathengine/trunk/lib/Tree/XPathEngine/Step.pm 25 2006-02-15T15:34:11.453583Z mrodrigu $
3              
4             package Tree::XPathEngine::Step;
5 5     5   30 use Tree::XPathEngine;
  5         8  
  5         152  
6 5     5   26 use strict;
  5         9  
  5         3872  
7              
8             # constants used to describe the test part of a step
9             sub test_name () { 0; } # Full name
10             sub test_any () { 1; } # *
11             sub test_attr_name () { 2; } # @attrib
12             sub test_attr_any () { 3; } # @*
13             sub test_nt_text () { 4; } # text()
14             sub test_nt_node () { 5; } # node()
15              
16             sub new {
17 275     275 1 397 my $class = shift;
18 275         491 my ($pp, $axis, $test, $literal) = @_;
19 275         475 my $axis_method = "axis_$axis";
20 275         461 $axis_method =~ tr/-/_/;
21 275         1612 my $self = {
22             pp => $pp, # the Tree::XPathEngine class
23             axis => $axis,
24             axis_method => $axis_method,
25             test => $test,
26             literal => $literal,
27             predicates => [],
28             };
29 275         3156 bless $self, $class;
30             }
31              
32             sub as_string {
33 0     0 1 0 my $self = shift;
34 0         0 my $string = $self->{axis} . "::";
35              
36 0         0 my $test = $self->{test};
37            
38 0 0       0 if ($test == test_nt_text) {
    0          
39 0         0 $string .= 'text()';
40             }
41             elsif ($test == test_nt_node) {
42 0         0 $string .= 'node()';
43             }
44             else {
45 0         0 $string .= $self->{literal};
46             }
47            
48 0         0 foreach (@{$self->{predicates}}) {
  0         0  
49 0 0       0 next unless defined $_;
50 0         0 $string .= "[" . $_->as_string . "]";
51             }
52 0         0 return $string;
53             }
54              
55             sub evaluate {
56 799     799 1 1117 my $self = shift;
57 799         855 my $from = shift; # context nodeset
58            
59             # warn "Step::evaluate called with ", $from->size, " length nodeset\n";
60            
61 799         5958 $self->{pp}->_set_context_set($from);
62            
63 799         2782 my $initial_nodeset = Tree::XPathEngine::NodeSet->new();
64            
65             # See spec section 2.1, paragraphs 3,4,5:
66             # The node-set selected by the location step is the node-set
67             # that results from generating an initial node set from the
68             # axis and node-test, and then filtering that node-set by
69             # each of the predicates in turn.
70            
71             # Make each node in the nodeset be the context node, one by one
72 799         2541 for(my $i = 1; $i <= $from->size; $i++) {
73 2108         6406 $self->{pp}->_set_context_pos($i);
74 2108         5903 $initial_nodeset->append($self->evaluate_node($from->get_node($i)));
75             }
76            
77             # warn "Step::evaluate initial nodeset size: ", $initial_nodeset->size, "\n";
78            
79 797         2674 $self->{pp}->_set_context_set(undef);
80              
81 797         2028 $initial_nodeset->sort;
82            
83 797         3929 return $initial_nodeset;
84             }
85              
86             # Evaluate the step against a particular node
87             sub evaluate_node {
88 2108     2108 1 2820 my $self = shift;
89 2108         2946 my $context = shift;
90            
91             # warn "Evaluate node: $self->{axis}\n";
92            
93             # warn "Node: ", $context->[node_name], "\n";
94            
95 2108         9200 my $method = $self->{axis_method};
96            
97 2108         6697 my $results = Tree::XPathEngine::NodeSet->new();
98 5     5   32 no strict 'refs';
  5         8  
  5         9197  
99 2108         3608 eval {
100             #$method->($self, $context, $results);
101 2108         7845 $self->$method( $context, $results);
102             };
103 2108 50       13756 if ($@) {
104 0         0 die "axis $method not implemented [$@]\n";
105             }
106            
107             # warn("results: ", join('><', map {$_->xpath_string_value} @$results), "\n");
108             # filter initial nodeset by each predicate
109 2108         2286 foreach my $predicate (@{$self->{predicates}}) {
  2108         11404  
110 1374         3061 $results = $self->filter_by_predicate($results, $predicate);
111             }
112            
113 2106         18120 return $results;
114             }
115              
116             sub axis_ancestor {
117 17     17 1 28 my $self = shift;
118 17         61 my ($context, $results) = @_;
119            
120 17         52 my $parent = $context->xpath_get_parent_node;
121            
122 17         466 while( $parent)
123 62 100       444 { $results->push($parent) if (node_test($self, $parent));
124 62         159 $parent = $parent->xpath_get_parent_node;
125             }
126 17 50       173 return $results unless $parent;
127             }
128              
129             sub axis_ancestor_or_self {
130 16     16 1 22 my $self = shift;
131 16         24 my ($context, $results) = @_;
132            
133 90 100       196 START:
134             return $results unless $context;
135 74 100       128 if (node_test($self, $context)) {
136 19         261 $results->push($context);
137             }
138 74         252 $context = $context->xpath_get_parent_node;
139 74         633 goto START;
140             }
141              
142             sub axis_attribute {
143 926     926 1 1022 my $self = shift;
144 926         1211 my ($context, $results) = @_;
145            
146 926         2440 foreach my $attrib ($context->xpath_get_attributes) {
147 2043 100       12158 if ($self->test_attribute($attrib)) {
148 822         2239 $results->push($attrib);
149             }
150             }
151             }
152              
153             sub axis_child {
154 825     825 1 944 my $self = shift;
155 825         955 my ($context, $results) = @_;
156            
157 825         2042 foreach my $node ($context->xpath_get_child_nodes) {
158 846 100       14041 if (node_test($self, $node)) {
159 482         22252 $results->push($node);
160             }
161             }
162             }
163              
164             sub axis_descendant {
165 8     8 1 13 my $self = shift;
166 8         14 my ($context, $results) = @_;
167              
168 8         32 my @stack = $context->xpath_get_child_nodes;
169              
170 8         191 while (@stack) {
171 128         1986 my $node = pop @stack;
172 128 100       237 if (node_test($self, $node)) {
173 27         704 $results->unshift($node);
174             }
175 128         296 push @stack, $node->xpath_get_child_nodes;
176             }
177             }
178              
179             sub axis_descendant_or_self {
180 91     91 1 174 my $self = shift;
181 91         8107 my ($context, $results) = @_;
182            
183 91         232 my @stack = ($context);
184            
185 91         434 while (@stack) {
186 1348         28725 my $node = pop @stack;
187 1348 50       2569 if (node_test($self, $node)) {
188 1348         3852 $results->unshift($node);
189             }
190 1348         5619 push @stack, $node->xpath_get_child_nodes;
191             }
192             }
193              
194             sub axis_following {
195 1     1 1 2 my $self = shift;
196 1         3 my ($context, $results) = @_;
197            
198 3         11 START:
199              
200             my $parent = $context->xpath_get_parent_node;
201 3 100       32 return $results unless $parent;
202            
203 2         7 while ($context = $context->xpath_get_next_sibling) {
204 1         15 axis_descendant_or_self($self, $context, $results);
205             }
206              
207 2         39 $context = $parent;
208 2         18 goto START;
209             }
210              
211             sub axis_following_sibling {
212 3     3 1 6 my $self = shift;
213 3         9 my ($context, $results) = @_;
214              
215 3         14 while ($context = $context->xpath_get_next_sibling) {
216 3 50       31 if (node_test($self, $context)) {
217 3         46 $results->push($context);
218             }
219             }
220             }
221              
222             sub axis_parent {
223 12     12 1 19 my $self = shift;
224 12         19 my ($context, $results) = @_;
225            
226 12         39 my $parent = $context->xpath_get_parent_node;
227 12 50       131 return $results unless $parent;
228 12 50       28 if (node_test($self, $parent)) {
229 12         37 $results->push($parent);
230             }
231             }
232              
233             sub axis_preceding {
234 1     1 1 3 my $self = shift;
235 1         2 my ($context, $results) = @_;
236            
237             # all preceding nodes in document order, except ancestors
238            
239 3         8 START:
240              
241             my $parent = $context->xpath_get_parent_node;
242 3 100       24 return $results unless $parent;
243              
244 2         8 while ($context = $context->xpath_get_previous_sibling) {
245 3         47 axis_descendant_or_self($self, $context, $results);
246             }
247            
248 2         23 $context = $parent;
249 2         5 goto START;
250             }
251              
252             sub axis_preceding_sibling {
253 2     2 1 5 my $self = shift;
254 2         5 my ($context, $results) = @_;
255            
256 2         9 while ($context = $context->xpath_get_previous_sibling) {
257 6 50       66 if (node_test($self, $context)) {
258 6         129 $results->push($context);
259             }
260             }
261             }
262              
263             sub axis_self {
264 210     210 1 264 my $self = shift;
265 210         282 my ($context, $results) = @_;
266            
267 210 50       359 if (node_test($self, $context)) {
268 210         758 $results->push($context);
269             }
270             }
271            
272             sub node_test {
273 2689     2689 1 3161 my $self = shift;
274 2689         3487 my $node = shift;
275            
276             # if node passes test, return true
277            
278 2689         4090 my $test = $self->{test};
279              
280 2689 100       8462 return 1 if $test == test_nt_node;
281            
282 1131 100       1980 if ($test == test_any) {
283 288 50 33     882 return 1 if( $node->xpath_is_element_node && defined $node->xpath_get_name);
284             }
285            
286 843         1916 local $^W;
287              
288 843 50       1448 if ($test == test_name) {
    0          
289 843 100       2113 return unless $node->xpath_is_element_node;
290 828 100       10965 return 1 if $node->xpath_get_name eq $self->{literal};
291             }
292             elsif ($test == test_nt_text) {
293 0 0       0 return 1 if $node->xpath_is_text_node;
294             }
295 545         6908 return; # fallthrough returns false
296             }
297              
298             sub test_attribute {
299 2043     2043 1 2547 my $self = shift;
300 2043         14398 my $node = shift;
301            
302             # warn "test_attrib: '$self->{test}' against: ", $node->xpath_get_name, "\n";
303             # warn "node type: $node->[node_type]\n";
304            
305 2043         2877 my $test = $self->{test};
306              
307 2043 100 66     16122 if( ($test == test_attr_any) || ($test == test_nt_node)
      66        
      66        
308             || ( ($test == test_attr_name) && ($node->xpath_get_name eq $self->{literal}) )
309             )
310 822         12055 { return 1; }
311             else
312 1221         17219 { return; }
313             }
314              
315              
316             sub filter_by_predicate {
317 1374     1374 1 1466 my $self = shift;
318 1374         1982 my ($nodeset, $predicate) = @_;
319            
320             # See spec section 2.4, paragraphs 2 & 3:
321             # For each node in the node-set to be filtered, the predicate Expr
322             # is evaluated with that node as the context node, with the number
323             # of nodes in the node set as the context size, and with the
324             # proximity position of the node in the node set with respect to
325             # the axis as the context position.
326            
327 1374 50       3551 if (!ref($nodeset)) { # use ref because nodeset has a bool context
328 0         0 die "No nodeset!!!";
329             }
330            
331             # warn "Filter by predicate: $predicate\n";
332            
333 1374         7103 my $newset = Tree::XPathEngine::NodeSet->new();
334            
335 1374         4374 for(my $i = 1; $i <= $nodeset->size; $i++) {
336             # set context set each time 'cos a loc-path in the expr could change it
337 713         2405 $self->{pp}->_set_context_set($nodeset);
338 713         2159 $self->{pp}->_set_context_pos($i);
339 713         1872 my $result = $predicate->evaluate($nodeset->get_node($i));
340 711 100       3218 if ($result->isa('Tree::XPathEngine::Boolean')) {
    100          
341 587 100       1585 if ($result->value) {
342 159         903 $newset->push($nodeset->get_node($i));
343             }
344             }
345             elsif ($result->isa('Tree::XPathEngine::Number')) {
346 93 100       310 if ($result->value == $i) {
347 22         76 $newset->push($nodeset->get_node($i));
348             }
349             }
350             else {
351 31 100       100 if ($result->xpath_to_boolean->value) {
352 17         56 $newset->push($nodeset->get_node($i));
353             }
354             }
355             }
356            
357 1372         5051 return $newset;
358             }
359              
360             1;
361              
362             __END__