File Coverage

blib/lib/Data/DPath/Path.pm
Criterion Covered Total %
statement 84 86 97.6
branch 23 24 95.8
condition 16 17 94.1
subroutine 17 18 94.4
pod 7 7 100.0
total 147 152 96.7


line stmt bran cond sub pod time code
1             package Data::DPath::Path;
2             our $AUTHORITY = 'cpan:SCHWIGON';
3             # ABSTRACT: Abstraction for a DPath
4             $Data::DPath::Path::VERSION = '0.60';
5 15     15   1748967 use strict;
  15         35  
  15         568  
6 15     15   72 use warnings;
  15         24  
  15         800  
7              
8 15     15   12710 use Data::Dumper;
  15         127991  
  15         1371  
9 15     15   14775 use aliased 'Data::DPath::Step';
  15         12957  
  15         4657  
10 15     15   1721 use aliased 'Data::DPath::Point';
  15         26  
  15         78  
11 15     15   1713 use aliased 'Data::DPath::Context';
  15         26  
  15         82  
12 15     15   14232 use Text::Balanced 2.02 'extract_delimited', 'extract_codeblock';
  15         200279  
  15         2322  
13              
14             use Class::XSAccessor
15 15         210 chained => 1,
16             accessors => {
17             path => 'path',
18             _steps => '_steps',
19             give_references => 'give_references',
20 15     15   302 };
  15         32  
21              
22 15         14722 use constant { ROOT => 'ROOT',
23             ANYWHERE => 'ANYWHERE',
24             KEY => 'KEY',
25             ANYSTEP => 'ANYSTEP',
26             NOSTEP => 'NOSTEP',
27             PARENT => 'PARENT',
28             ANCESTOR => 'ANCESTOR',
29             ANCESTOR_OR_SELF => 'ANCESTOR_OR_SELF',
30 15     15   5625 };
  15         33  
31              
32             sub new {
33 447     447 1 44275 my $class = shift;
34 447         1950 my $self = bless { @_ }, $class;
35 447         1931 $self->_build__steps;
36 447         2380 return $self;
37             }
38              
39             sub unescape {
40 1784     1784 1 6135 my ($str) = @_;
41              
42 1784 50       4142 return unless defined $str;
43 1784         4140 $str =~ s/\\{2}/\\/g;
44 1784         3247 $str =~ s/\\(["'])/$1/g; # '"$
45 1784         5812 return $str;
46             }
47              
48             sub unquote {
49 39     39 1 80 my ($str) = @_;
50 39         227 $str =~ s/^"(.*)"$/$1/g;
51 39         124 return $str;
52             }
53              
54 1779     1779 1 7149 sub quoted { shift =~ m,^/["'],; } # "
55              
56             eval 'use overload "~~" => \&op_match' if $] >= 5.010 and $] < 5.041003;
57              
58             sub op_match {
59 0     0 1 0 my ($self, $data, $rhs) = @_;
60              
61 0         0 return $self->matchr( $data );
62             }
63              
64             # essentially the Path parser
65             sub _build__steps {
66 447     447   984 my ($self) = @_;
67              
68 447         1473 my $remaining_path = $self->path;
69 447         809 my $extracted;
70             my @steps;
71              
72 447         5152 push @steps, Step->new->part('')->kind(ROOT);
73              
74 447         1508 while ($remaining_path) {
75 1779         4304 my $plain_part;
76             my $filter;
77 1779         0 my $kind;
78 1779 100       3839 if ( quoted($remaining_path) ) {
79 39         162 ($plain_part, $remaining_path) = extract_delimited($remaining_path, q/'"/, "/"); # '
80 39         5361 ($filter, $remaining_path) = extract_codeblock($remaining_path, "[]");
81 39         6338 $plain_part = unescape unquote $plain_part;
82 39         81 $kind = KEY; # quoted is always a key
83             }
84             else
85             {
86 1740         2705 my $filter_already_extracted = 0;
87 1740         4818 ($extracted, $remaining_path) = extract_delimited($remaining_path,'/');
88              
89 1740 100       144759 if (not $extracted) {
90 430         1041 ($extracted, $remaining_path) = ($remaining_path, undef); # END OF PATH
91             } else {
92              
93             # work around to recognize slashes in filter expressions and handle them:
94             #
95             # - 1) see if key unexpectedly contains opening "[" but no closing "]"
96             # - 2) use the part before "["
97             # - 3) unshift the rest to remaining
98             # - 4) extract_codeblock() explicitely
99 1310 100 100     5443 if ($extracted =~ /(.*)((?
100 32         140 $remaining_path = $2 . $remaining_path;
101 32         146 ( $plain_part = $1 ) =~ s|^/||;
102 32         128 ($filter, $remaining_path) = extract_codeblock($remaining_path, "[]");
103 32         16123 $filter_already_extracted = 1;
104             } else {
105 1278         3222 $remaining_path = (chop $extracted) . $remaining_path;
106             }
107             }
108              
109 1740 100       11010 ($plain_part, $filter) = $extracted =~ m,^/ # leading /
110             (.*?) # path part
111             (\[.*\])?$ # optional filter
112             ,xsg unless $filter_already_extracted;
113 1740         4352 $plain_part = unescape $plain_part;
114             }
115              
116 15     15   137 no warnings 'uninitialized';
  15         48  
  15         8910  
117 1779 100 100     6969 if ($plain_part eq '') { $kind ||= ANYWHERE }
  295 100       1399  
    100          
    100          
    100          
    100          
118 293   100     1159 elsif ($plain_part eq '*') { $kind ||= ANYSTEP }
119 125   100     482 elsif ($plain_part eq '.') { $kind ||= NOSTEP }
120 107   100     415 elsif ($plain_part eq '..') { $kind ||= PARENT }
121 19   50     132 elsif ($plain_part eq '::ancestor') { $kind ||= ANCESTOR }
122 21   100     94 elsif ($plain_part eq '::ancestor-or-self') { $kind ||= ANCESTOR_OR_SELF }
123 919   100     3564 else { $kind ||= KEY }
124              
125 1779         11998 push @steps, Step->new->part($plain_part)->kind($kind)->filter($filter);
126             }
127 447 100       1612 pop @steps if $steps[-1]->kind eq ANYWHERE; # ignore final '/'
128 447         1833 $self->_steps( \@steps );
129             }
130              
131             sub match {
132 387     387 1 796 my ($self, $data) = @_;
133              
134 387         546 return @{$self->matchr($data)};
  387         981  
135             }
136              
137             sub matchr {
138 423     423 1 994 my ($self, $data) = @_;
139              
140 423         4719 my $context = Context
141             ->new
142             ->current_points([ Point->new->ref(\$data) ])
143             ->give_references($self->give_references);
144 423         4601 return $context->matchr($self);
145             }
146              
147             1;
148              
149             __END__