File Coverage

blib/lib/PPIx/XPath.pm
Criterion Covered Total %
statement 197 242 81.4
branch 21 38 55.2
condition 5 27 18.5
subroutine 64 97 65.9
pod 0 38 0.0
total 287 442 64.9


line stmt bran cond sub pod time code
1             package PPIx::XPath;
2 3     3   976146 use strict;
  3         7  
  3         129  
3 3     3   16 use warnings;
  3         7  
  3         104  
4 3     3   1285 use PPI;
  3         248808  
  3         84  
5 3     3   24 use Carp;
  3         6  
  3         300  
6 3     3   22 use Scalar::Util qw(reftype blessed);
  3         5  
  3         169  
7 3     3   3866 use Tree::XPathEngine;
  3         140224  
  3         116  
8 3     3   114 use 5.006;
  3         11  
  3         833  
9              
10             our $VERSION='2.01';
11              
12             sub new {
13 1     1 0 19 my ($class,$source) = @_;
14              
15 1 50       6 croak "PPIx::XPath->new needs a source document" unless defined($source);
16              
17 1         3 my $doc;
18 1 50 33     18 if (blessed($source) && $source->isa('PPI::Node')) {
    50 0        
      33        
19 0         0 $doc = $source;
20             }
21             elsif (reftype($source) eq 'SCALAR'
22             or (!ref($source) && -f $source)) {
23 1         16 $doc = PPI::Document->new($source);
24             }
25             else {
26 0   0     0 croak "PPIx::XPath expects either a PPI::Node or a file" .
27             " got a: [" .( ref($source) || $source ). ']';
28             }
29              
30 1         4856 return bless {doc=>$doc},$class;
31             }
32              
33             # PPIx::XPath 1.0.0 allowed the use of partial package names as node names;
34             # this collides with the axis specification of proper XPath.
35             # Here we change the "old-style" node names into the new names
36             {
37             my $legacy_names_rx;my %new_name_for;
38             sub clean_xpath_expr {
39 3     3 0 9 my (undef,$expr)=@_;
40              
41 3         40 $expr =~ s{$legacy_names_rx}{$new_name_for{$1}}ge;
  5         36  
42              
43 3         12 return $expr;
44             }
45              
46             my @PPI_Packs;
47             # taken from Devel::Symdump
48             my @packages=('PPI');
49             while (my $pack=shift(@packages)) {
50 3     3   22 no strict 'refs';
  3         4  
  3         1636  
51             while (my ($key,$val)=each(%{*{"$pack\::"}})) {
52             local *ENTRY=$val;
53             if (defined $val && defined *ENTRY{HASH} && $key=~/::$/
54             && $key !~ /^::/
55             && $key ne 'main::' && $key ne '::') {
56              
57             my $p = "$pack\::$key";$p =~ s{::$}{}; #}{};
58             push @packages,$p;
59             $p =~ s{^PPI::}{};
60              
61             next unless $p=~/::/;
62              
63             my $newname=$p;
64             $newname =~ s{::}{-}g;
65             push @PPI_Packs,$p;
66             $new_name_for{$p}=$newname;
67             }
68             }
69             }
70             $legacy_names_rx='\b('.join('|',
71             sort {length($b) <=> length($a)} @PPI_Packs
72             ).')\b';
73             $legacy_names_rx=qr{$legacy_names_rx};
74             }
75              
76             sub match {
77 3     3 0 2086 my ($self,$expr) = @_;
78              
79 3         15 $expr=$self->clean_xpath_expr($expr);
80              
81 3         25 Tree::XPathEngine->new()->findnodes($expr,$self->{doc});
82             }
83              
84             package PPI::Element;
85 3     3   19 use strict;
  3         5  
  3         317  
86 3     3   38 use warnings;
  3         4  
  3         5662  
87              
88 67     67 0 477 sub xpath_get_name { my $pack_name=substr($_[0]->class,5);
89 67         390 $pack_name =~ s/::/-/g;
90 67         195 $pack_name }
91 0     0 0 0 sub xpath_get_next_sibling { $_[0]->snext_sibling }
92 0     0 0 0 sub xpath_get_previous_sibling { $_[0]->sprevious_sibling }
93 0     0 0 0 sub xpath_get_root_node { $_[0]->top }
94 0     0 0 0 sub xpath_get_parent_node { $_[0]->parent }
95 67     67 0 1429 sub xpath_is_element_node { 1 }
96 0     0 0 0 sub xpath_is_attribute_node { 0 }
97 0     0 0 0 sub xpath_is_document_node { 0 }
98             sub xpath_get_attributes {
99             return
100 4     4 0 16 PPIx::XPath::Attr->new($_[0],'significant'),
101             PPIx::XPath::Attr->new($_[0],'content'),
102             }
103 0     0 0 0 sub xpath_to_literal { "$_[0]" }
104 0     0 0 0 sub xpath_get_value { "$_[0]" }
105 1     1 0 97 sub xpath_string_value { "$_[0]" }
106              
107             sub xpath_cmp {
108 63     63 0 973 my( $a, $b)= @_;
109 63 50       459 if ( UNIVERSAL::isa( $b, 'PPIx::XPath::Attr')) {
    50          
110             # elt <=> att, compare the elt to the att->{elt}
111             # if the elt is the att->{elt} (cmp return 0) then -1, elt is before att
112 0   0     0 return ($a->_xpath_elt_cmp( $b->{parent}) ) || -1 ;
113             }
114             elsif ( UNIVERSAL::isa( $b, 'PPI::Document')) {
115             # elt <=> document, elt is after document
116 0         0 return 1;
117             } else {
118             # 2 elts, compare them
119 63         152 return $a->_xpath_elt_cmp( $b);
120             }
121             }
122              
123             sub _xpath_elt_cmp {
124 63     63   78 my ($a,$b)=@_;
125              
126             # easy cases
127 63 50       129 return 0 if( $a == $b);
128 63 100       387 return 1 if( $a->_xpath_in($b)); # a starts after b
129 48 100       533 return -1 if( $b->_xpath_in($a)); # a starts before b
130              
131             # ancestors does not include the element itself
132 42         469 my @a_pile= ($a, $a->_xpath_ancestors);
133 42         99 my @b_pile= ($b, $b->_xpath_ancestors);
134              
135             # the 2 elements are not in the same twig
136 42 50       96 return undef unless( $a_pile[-1] == $b_pile[-1]);
137              
138             # find the first non common ancestors (they are siblings)
139 42         221 my $a_anc= pop @a_pile;
140 42         49 my $b_anc= pop @b_pile;
141              
142 42         87 while( $a_anc == $b_anc) {
143 83         402 $a_anc= pop @a_pile;
144 83         176 $b_anc= pop @b_pile;
145             }
146              
147             # from there move left and right and figure out the order
148 42         217 my( $a_prev, $a_next, $b_prev, $b_next)= ($a_anc, $a_anc, $b_anc, $b_anc);
149 42         82 while () {
150 42   100     127 $a_prev= $a_prev->sprevious_sibling || return( -1);
151 19 100       436 return 1 if( $a_prev == $b_next);
152 3   50     33 $a_next= $a_next->snext_sibling || return( 1);
153 3 50       65 return -1 if( $a_next == $b_prev);
154 0   0     0 $b_prev= $b_prev->sprevious_sibling || return( 1);
155 0 0       0 return -1 if( $b_prev == $a_next);
156 0   0     0 $b_next= $b_next->snext_sibling || return( -1);
157 0 0       0 return 1 if( $b_next == $a_prev);
158             }
159             }
160              
161             sub _xpath_in {
162 111     111   141 my ($self, $ancestor)= @_;
163 111         287 while ( $self= $self->parent) {
164 250 100       2133 return $self if ( $self == $ancestor);
165             }
166             }
167              
168             sub _xpath_ancestors {
169 84     84   99 my( $self)= @_;
170 84         94 my @ancestors;
171 84         210 while ( $self= $self->parent) {
172 213         1338 push @ancestors, $self;
173             }
174 84         440 return @ancestors;
175             }
176              
177             package PPI::Token;
178 3     3   21 use strict;
  3         4  
  3         211  
179 3     3   21 use warnings;
  3         5  
  3         235  
180              
181 28     28 0 895 sub xpath_get_child_nodes { return }
182              
183             package PPI::Token::Quote::Double;
184 3     3   14 use strict;
  3         17  
  3         181  
185 3     3   15 use warnings;
  3         5  
  3         273  
186              
187             sub xpath_get_attributes {
188 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
189             PPIx::XPath::Attr->new($_[0],'interpolations'),
190             }
191              
192             package PPI::Token::Number;
193 3     3   16 use strict;
  3         5  
  3         95  
194 3     3   13 use warnings;
  3         4  
  3         253  
195              
196             sub xpath_get_attributes {
197 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
198             PPIx::XPath::Attr->new($_[0],'base'),
199             }
200              
201             package PPI::Token::Word;
202 3     3   14 use strict;
  3         4  
  3         76  
203 3     3   12 use warnings;
  3         6  
  3         258  
204              
205             sub xpath_get_attributes {
206 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
207             PPIx::XPath::Attr->new($_[0],'method-call'),
208             }
209              
210             package PPI::Token::Comment;
211 3     3   15 use strict;
  3         5  
  3         94  
212 3     3   15 use warnings;
  3         12  
  3         5576  
213              
214             sub xpath_get_attributes {
215 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
216             PPIx::XPath::Attr->new($_[0],'line'),
217             }
218              
219             package PPI::Token::HereDoc;
220 3     3   24 use strict;
  3         5  
  3         92  
221 3     3   14 use warnings;
  3         7  
  3         3774  
222              
223             # TODO: add access to the contents of the heredoc (->heredoc method)
224              
225             sub xpath_get_attributes {
226 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
227             PPIx::XPath::Attr->new($_[0],'terminator'),
228             }
229              
230             package PPI::Token::Prototype;
231 3     3   22 use strict;
  3         5  
  3         94  
232 3     3   14 use warnings;
  3         27  
  3         759  
233              
234 0     0 0 0 sub xpath_to_literal { $_[0]->prototype }
235 0     0 0 0 sub xpath_get_value { $_[0]->prototype }
236 0     0 0 0 sub xpath_string_value { $_[0]->prototype }
237              
238             package PPI::Node;
239 3     3   18 use strict;
  3         5  
  3         200  
240 3     3   15 use warnings;
  3         5  
  3         460  
241              
242 42     42 0 3170 sub xpath_get_child_nodes { $_[0]->schildren }
243             sub xpath_get_attributes {
244 4     4 0 19 return $_[0]->SUPER::xpath_get_attributes,
245             PPIx::XPath::Attr->new($_[0],'scope'),
246             }
247              
248             package PPI::Token::Attribute;
249 3     3   26 use strict;
  3         5  
  3         107  
250 3     3   14 use warnings;
  3         10  
  3         320  
251              
252             sub xpath_get_attributes {
253 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
254             PPIx::XPath::Attr->new($_[0],'identifier'),
255             PPIx::XPath::Attr->new($_[0],'parameters'),
256             }
257              
258             package PPI::Token::Symbol;
259 3     3   16 use strict;
  3         4  
  3         94  
260 3     3   21 use warnings;
  3         4  
  3         467  
261              
262             sub xpath_get_attributes {
263 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
264             PPIx::XPath::Attr->new($_[0],'symbol'),
265             PPIx::XPath::Attr->new($_[0],'canonical'),
266             PPIx::XPath::Attr->new($_[0],'raw_type'),
267             PPIx::XPath::Attr->new($_[0],'symbol_typel'),
268             }
269              
270             package PPI::Statement;
271 3     3   17 use strict;
  3         45  
  3         135  
272 3     3   22 use warnings;
  3         7  
  3         527  
273              
274             sub xpath_get_attributes {
275 4     4 0 15 return $_[0]->SUPER::xpath_get_attributes,
276             PPIx::XPath::Attr->new($_[0],'label'),
277             PPIx::XPath::Attr->new($_[0],'stable'),
278             PPIx::XPath::Attr->new($_[0],'type'),
279             }
280              
281             package PPI::Statement::Sub;
282 3     3   16 use strict;
  3         4  
  3         94  
283 3     3   14 use warnings;
  3         4  
  3         407  
284              
285             sub xpath_get_attributes {
286 4     4 0 508 return $_[0]->SUPER::xpath_get_attributes,
287             PPIx::XPath::Attr->new($_[0],'name'),
288             PPIx::XPath::Attr->new($_[0],'prototype'),
289             PPIx::XPath::Attr->new($_[0],'forward'),
290             PPIx::XPath::Attr->new($_[0],'reserved'),
291             }
292              
293             package PPI::Statement::Package;
294 3     3   15 use strict;
  3         5  
  3         107  
295 3     3   19 use warnings;
  3         5  
  3         264  
296              
297             sub xpath_get_attributes {
298 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
299             PPIx::XPath::Attr->new($_[0],'namespace'),
300             PPIx::XPath::Attr->new($_[0],'file-scoped'),
301             }
302              
303             package PPI::Statement::Include;
304 3     3   16 use strict;
  3         3  
  3         103  
305 3     3   13 use warnings;
  3         4  
  3         541  
306              
307             sub xpath_get_attributes {
308 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
309             PPIx::XPath::Attr->new($_[0],'module'),
310             PPIx::XPath::Attr->new($_[0],'module-version'),
311             PPIx::XPath::Attr->new($_[0],'version'),
312             PPIx::XPath::Attr->new($_[0],'version-literal'),
313             PPIx::XPath::Attr->new($_[0],'pragma'),
314             }
315              
316             package PPI::Structure;
317 3     3   16 use strict;
  3         5  
  3         100  
318 3     3   14 use warnings;
  3         4  
  3         319  
319              
320             sub xpath_get_attributes {
321 0     0 0 0 return $_[0]->SUPER::xpath_get_attributes,
322             PPIx::XPath::Attr->new($_[0],'start'),
323             PPIx::XPath::Attr->new($_[0],'finish'),
324             PPIx::XPath::Attr->new($_[0],'braces'),
325             }
326              
327             package PPI::Document;
328 3     3   15 use strict;
  3         4  
  3         91  
329 3     3   25 use warnings;
  3         5  
  3         323  
330              
331 7     7 0 29051 sub xpath_get_root_node { $_[0] }
332 0     0 0 0 sub xpath_get_parent_node { return }
333 0     0 0 0 sub xpath_is_attribute_node { 0 }
334 0     0 0 0 sub xpath_is_document_node { 1 }
335              
336             package PPIx::XPath::Attr;
337 3     3   14 use strict;
  3         5  
  3         70  
338 3     3   15 use warnings;
  3         5  
  3         1804  
339              
340             sub new {
341 40     40   94 my ($class,$parent,$name)=@_;
342              
343 40         108 my $meth=$parent->can($name);
344 40 100       84 return unless $meth;
345              
346 36         33 my $value;
347 36 100       36 eval {$value=$meth->($parent);1} or return;
  36         78  
  32         854  
348              
349 32 50       58 return unless defined $value;
350              
351 32         180 return bless {parent=>$parent,name=>$name,value=>$value},$class;
352             }
353              
354 32     32   338 sub xpath_get_name { $_[0]->{name} }
355 0     0   0 sub xpath_get_root_node { $_[0]->{parent}->top }
356 0     0   0 sub xpath_get_parent_node { $_[0]->{parent} }
357 0     0   0 sub xpath_is_element_node { 0 }
358 0     0   0 sub xpath_is_attribute_node { 1 }
359 0     0   0 sub xpath_is_document_node { 0 }
360 0     0   0 sub xpath_to_literal { $_[0]->{value} }
361 0     0   0 sub xpath_get_value { $_[0]->{value} }
362 4     4   461 sub xpath_string_value { $_[0]->{value} }
363 0     0     sub xpath_to_number { Tree::XPathEngine::Number->new($_[0]->{value}) }
364              
365             sub xpath_cmp($$) {
366 0     0     my( $a, $b)= @_;
367 0 0         if ( UNIVERSAL::isa( $b, 'PPIx::XPath::Attr')) {
    0          
368             # 2 attributes, compare their elements, then their name
369 0   0       return ($a->{parent}->_xpath_elt_cmp( $b->{parent}) )
370             || ($a->{name} cmp $b->{name});
371             }
372             elsif ( UNIVERSAL::isa( $b, 'PPI::Document')) {
373             # att <=> document, att is after document
374 0           return 1;
375             }
376             else {
377             # att <=> elt : compare the att->elt and the elt
378             # if att->elt is the elt (cmp returns 0) then 1 (elt is before att)
379 0   0       return ($a->{parent}->_xpath_elt_cmp( $b) ) || 1 ;
380             }
381             }
382              
383             1;
384             __END__