| 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; | |||||