File Coverage

File:lib/XML/Parser/Lite/Tree/XPath/Axis.pm
Coverage:95.8%

linestmtbrancondsubtimecode
1package XML::Parser::Lite::Tree::XPath::Axis;
2
3
33
33
33
161
52
125
use strict;
4
33
33
33
145
52
196
use XML::Parser::Lite::Tree::XPath::Result;
5
6
33
33
33
139
96
205
use vars qw( $axis );
7
8sub instance {
9
280
1334
        return $axis if $axis;
10
25
116
        $axis = __PACKAGE__->new;
11}
12
13sub new {
14
25
224
        return bless {}, $_[0];
15}
16
17sub filter {
18
280
890
        my ($self, $token, $context) = @_;
19
20
280
790
        $self->{token} = $token;
21
280
1301
        $self->{axis} = defined($token->{axis}) ? $token->{axis} : 'child';
22
23
280
1354
        return $self->_axis_child($context) if $self->{axis} eq 'child';
24
115
440
        return $self->_axis_descendant($context, 0) if $self->{axis} eq 'descendant';
25
109
722
        return $self->_axis_descendant($context, 1) if $self->{axis} eq 'descendant-or-self';
26
49
194
        return $self->_axis_parent($context) if $self->{axis} eq 'parent';
27
47
185
        return $self->_axis_ancestor($context, 0) if $self->{axis} eq 'ancestor';
28
43
157
        return $self->_axis_ancestor($context, 1) if $self->{axis} eq 'ancestor-or-self';
29
41
156
        return $self->_axis_following_sibling($context) if $self->{axis} eq 'following-sibling';
30
39
140
        return $self->_axis_preceding_sibling($context) if $self->{axis} eq 'preceding-sibling';
31
37
140
        return $self->_axis_following($context) if $self->{axis} eq 'following';
32
33
183
        return $self->_axis_preceding($context) if $self->{axis} eq 'preceding';
33
29
152
        return $self->_axis_attribute($context) if $self->{axis} eq 'attribute';
34
35
3
16
        return $context if $self->{axis} eq 'self';
36
37
1
6
        return $self->ret('Error', "Unknown axis '$self->{axis}'");
38}
39
40sub ret {
41
278
870
        my ($self, $type, $value) = @_;
42
278
1064
        return XML::Parser::Lite::Tree::XPath::Result->new($type, $value);
43}
44
45sub _axis_child {
46
165
446
        my ($self, $in) = @_;
47
48
165
619
        my $out = $self->ret('nodeset', []);
49
50
165
165
415
577
        for my $tag(@{$in->{value}}){
51
745
745
1247
2822
                for my $child(@{$tag->{children}}){
52
757
757
1160
3355
                        push @{$out->{value}}, $child;
53                }
54        }
55
56
165
543
        return $out;
57}
58
59sub _axis_descendant {
60
66
214
        my ($self, $in, $me) = @_;
61
62
66
250
        my $out = $self->ret('nodeset', []);
63
64
66
66
132
241
        for my $tag(@{$in->{value}}){
65
66
682
2537
                map{
67
70
682
348
1151
                        push @{$out->{value}}, $_;
68
69                }$self->_axis_descendant_single($tag, $me);
70        }
71
72
66
228
        return $out;
73}
74
75sub _axis_descendant_single {
76
690
1917
        my ($self, $tag, $me) = @_;
77
78
690
958
        my @out;
79
80
690
2293
        push @out, $tag if $me;
81
82
690
690
1088
2473
        for my $child(@{$tag->{children}}){
83
84
622
2354
                if ($child->{type} eq 'element'){
85
86
1788
5329
                        map{
87
620
1916
                                push @out, $_;
88                        }$self->_axis_descendant_single($child, 1);
89                }
90        }
91
92
690
1940
        return @out;
93}
94
95sub _axis_attribute {
96
26
66
        my ($self, $input) = @_;
97
98
26
85
        my $out = $self->ret('nodeset', []);
99
26
60
        my $nodes = [];
100
101
26
98
        if ($input->{type} eq 'nodeset'){
102
26
72
                $nodes = $input->{value};
103        }
104
105
26
103
        if ($input->{type} eq 'node'){
106
0
0
                $nodes = [$input->{value}];
107        }
108
109
26
67
        return $self->ret('Error', "attribute axis can only filter nodes and nodesets (not a $input->{type})") unless defined $nodes;
110
111
26
44
        my $i = 0;
112
113
26
26
49
69
        for my $node(@{$nodes}){
114
31
31
49
197
                for my $key(keys %{$node->{attributes}}){
115
30
30
49
347
                        push @{$out->{value}}, {
116                                'name' => $key,
117                                'value' => $node->{attributes}->{$key},
118                                'type' => 'attribute',
119                                'order' => ($node->{order} * 10000000) + $i++,
120                        };
121                }
122        }
123
124
26
89
        return $out;
125}
126
127sub _axis_parent {
128
2
5
        my ($self, $in) = @_;
129
130
2
8
        my $out = $self->ret('nodeset', []);
131
132
2
2
4
7
        for my $tag(@{$in->{value}}){
133
6
6
24
28
                push @{$out->{value}}, $tag->{parent} if defined $tag->{parent};
134        }
135
136
2
6
        return $out;
137}
138
139sub _axis_ancestor {
140
6
22
        my ($self, $in, $me) = @_;
141
142
6
23
        my $out = $self->ret('nodeset', []);
143
144
6
6
11
24
        for my $tag(@{$in->{value}}){
145
146
32
117
                map{
147
6
32
25
55
                        push @{$out->{value}}, $_;
148
149                }$self->_axis_ancestor_single($tag, $me);
150        }
151
152
6
22
        return $out;
153}
154
155sub _axis_ancestor_single {
156
36
101
        my ($self, $tag, $me) = @_;
157
158
36
52
        my @out;
159
160
36
121
        push @out, $tag if $me;
161
162
36
128
        if (defined $tag->{parent}){
163
164
91
287
                map{
165
30
134
                        push @out, $_;
166                }$self->_axis_ancestor_single($tag->{parent}, 1);
167        }
168
169
36
103
        return @out;
170}
171
172sub _axis_following_sibling {
173
2
7
        my ($self, $in) = @_;
174
175
2
7
        my $out = $self->ret('nodeset', []);
176
177
2
2
5
7
        for my $tag(@{$in->{value}}){
178
4
16
                if (defined $tag->{parent}){
179
4
10
                        my $parent = $tag->{parent};
180
4
7
                        my $found = 0;
181
4
4
7
13
                        for my $child(@{$parent->{children}}){
182
13
5
35
13
                                push @{$out->{value}}, $child if $found;
183
13
72
                                $found = 1 if $child->{order} == $tag->{order};
184                        }
185                }
186        }
187
188
2
9
        return $out;
189}
190
191sub _axis_preceding_sibling {
192
2
7
        my ($self, $in) = @_;
193
194
2
7
        my $out = $self->ret('nodeset', []);
195
196
2
2
4
9
        for my $tag(@{$in->{value}}){
197
4
14
                if (defined $tag->{parent}){
198
4
11
                        my $parent = $tag->{parent};
199
4
6
                        my $found = 0;
200
4
4
8
13
                        for my $child(@{$parent->{children}}){
201
13
56
                                $found = 1 if $child->{order} == $tag->{order};
202
13
5
47
18
                                push @{$out->{value}}, $child unless $found;
203                        }
204                }
205        }
206
207
2
7
        return $out;
208}
209
210sub _axis_following {
211
4
12
        my ($self, $in) = @_;
212
213
4
19
        my $min_order = 1 + $self->{token}->{max_order};
214
4
4
6
16
        for my $tag(@{$in->{value}}){
215
4
28
                $min_order = $tag->{order} if $tag->{order} < $min_order;
216        }
217
218        # recurse the whole tree, adding after we find $min_order (but don't descend into it!)
219
220
4
30
        my @tags = $self->_axis_following_recurse( $self->{token}->{root}->{value}->[0], $min_order );
221
222
4
19
        return $self->ret('nodeset', \@tags);
223}
224
225sub _axis_following_recurse {
226
54
153
        my ($self, $tag, $min) = @_;
227
228
54
74
        my @out;
229
230
54
207
        push @out, $tag if $tag->{order} > $min;
231
232
54
54
81
192
        for my $child(@{$tag->{children}}){
233
234
54
461
                if (($child->{order}) != $min && ($child->{type} eq 'element')){
235
236
67
253
                        map{
237
50
161
                                push @out, $_;
238                        }$self->_axis_following_recurse($child, $min);
239                }
240        }
241
242
54
158
        return @out;
243}
244
245sub _axis_preceding {
246
4
12
        my ($self, $in) = @_;
247
248
4
8
        my $max_order = -1;
249
4
8
        my $parents;
250
4
4
8
15
        for my $tag(@{$in->{value}}){
251
4
17
                if ($tag->{order} > $max_order){
252
4
12
                        $max_order = $tag->{order};
253
4
15
                        $parents = $self->_get_parent_orders($tag);
254                }
255        }
256
257        # recurse the whole tree, adding until we find $max_order (but don't descend into it!)
258
259
4
32
        my @tags = $self->_axis_preceding_recurse( $self->{token}->{root}->{value}->[0], $parents, $max_order );
260
261
4
17
        return $self->ret('nodeset', \@tags);
262}
263
264sub _axis_preceding_recurse {
265
49
154
        my ($self, $tag, $parents, $max) = @_;
266
267
49
72
        my @out;
268
269
49
383
        push @out, $tag if $tag->{order} < $max && !$parents->{$tag->{order}};
270
271
49
49
80
172
        for my $child(@{$tag->{children}}){
272
273
49
376
                if (($child->{order}) != $max && ($child->{type} eq 'element')){
274
275
74
233
                        map{
276
45
158
                                push @out, $_;
277                        }$self->_axis_preceding_recurse($child, $parents, $max);
278                }
279        }
280
281
49
147
        return @out;
282}
283
284sub _get_parent_orders {
285
4
14
        my ($self, $tag) = @_;
286
4
6
        my $parents;
287
288
4
17
        while(defined $tag->{parent}){
289
17
42
                $tag = $tag->{parent};
290
17
97
                $parents->{$tag->{order}} = 1;
291        }
292
293
4
17
        return $parents;
294}
295
2961;