File: | lib/XML/Parser/Lite/Tree/XPath/Axis.pm |
Coverage: | 95.8% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package 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 | ||||||
8 | sub instance { | |||||
9 | 280 | 1334 | return $axis if $axis; | |||
10 | 25 | 116 | $axis = __PACKAGE__->new; | |||
11 | } | |||||
12 | ||||||
13 | sub new { | |||||
14 | 25 | 224 | return bless {}, $_[0]; | |||
15 | } | |||||
16 | ||||||
17 | sub 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 | ||||||
40 | sub ret { | |||||
41 | 278 | 870 | my ($self, $type, $value) = @_; | |||
42 | 278 | 1064 | return XML::Parser::Lite::Tree::XPath::Result->new($type, $value); | |||
43 | } | |||||
44 | ||||||
45 | sub _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 | ||||||
59 | sub _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 | ||||||
75 | sub _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 | ||||||
95 | sub _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 | ||||||
127 | sub _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 | ||||||
139 | sub _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 | ||||||
155 | sub _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 | ||||||
172 | sub _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 | ||||||
191 | sub _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 | ||||||
210 | sub _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 | ||||||
225 | sub _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 | ||||||
245 | sub _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 | ||||||
264 | sub _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 | ||||||
284 | sub _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 | ||||||
296 | 1; |