File Coverage

blib/lib/Data/DPath/Path.pm
Criterion Covered Total %
statement 89 89 100.0
branch 23 24 95.8
condition 16 17 94.1
subroutine 19 19 100.0
pod 7 7 100.0
total 154 156 98.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.58';
5 12     12   1555 use strict;
  12         24  
  12         321  
6 12     12   57 use warnings;
  12         21  
  12         268  
7              
8 12     12   6345 use Data::Dumper;
  12         71331  
  12         821  
9 12     12   5247 use aliased 'Data::DPath::Step';
  12         8240  
  12         69  
10 12     12   1198 use aliased 'Data::DPath::Point';
  12         23  
  12         58  
11 12     12   956 use aliased 'Data::DPath::Context';
  12         26  
  12         64  
12 12     12   9072 use Text::Balanced 2.02 'extract_delimited', 'extract_codeblock';
  12         175786  
  12         1349  
13              
14             use Class::XSAccessor
15 12         144 chained => 1,
16             accessors => {
17             path => 'path',
18             _steps => '_steps',
19             give_references => 'give_references',
20 12     12   112 };
  12         24  
21              
22 12         7156 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 12     12   3811 };
  12         30  
31              
32             sub new {
33 460     460 1 22851 my $class = shift;
34 460         1428 my $self = bless { @_ }, $class;
35 460         1352 $self->_build__steps;
36 460         3844 return $self;
37             }
38              
39             sub unescape {
40 1850     1850 1 5705 my ($str) = @_;
41              
42 1850 50       3428 return unless defined $str;
43 1850         3032 $str =~ s/\\{2}/\\/g;
44 1850         2557 $str =~ s/\\(["'])/$1/g; # '"$
45 1850         3568 return $str;
46             }
47              
48             sub unquote {
49 39     39 1 75 my ($str) = @_;
50 39         194 $str =~ s/^"(.*)"$/$1/g;
51 39         99 return $str;
52             }
53              
54 1845     1845 1 6670 sub quoted { shift =~ m,^/["'],; } # "
55              
56 12     12   92 eval 'use overload "~~" => \&op_match' if $] >= 5.010;
  12         26  
  12         62  
57              
58             sub op_match {
59 158     158 1 389 my ($self, $data, $rhs) = @_;
60              
61 158         374 return $self->matchr( $data );
62             }
63              
64             # essentially the Path parser
65             sub _build__steps {
66 460     460   770 my ($self) = @_;
67              
68 460         1205 my $remaining_path = $self->path;
69 460         1131 my $extracted;
70             my @steps;
71              
72 460         2424 push @steps, Step->new->part('')->kind(ROOT);
73              
74 460         1261 while ($remaining_path) {
75 1845         4046 my $plain_part;
76             my $filter;
77 1845         0 my $kind;
78 1845 100       3191 if ( quoted($remaining_path) ) {
79 39         124 ($plain_part, $remaining_path) = extract_delimited($remaining_path, q/'"/, "/"); # '
80 39         3760 ($filter, $remaining_path) = extract_codeblock($remaining_path, "[]");
81 39         3959 $plain_part = unescape unquote $plain_part;
82 39         81 $kind = KEY; # quoted is always a key
83             }
84             else
85             {
86 1806         2554 my $filter_already_extracted = 0;
87 1806         4058 ($extracted, $remaining_path) = extract_delimited($remaining_path,'/');
88              
89 1806 100       113542 if (not $extracted) {
90 443         898 ($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 1363 100 100     4582 if ($extracted =~ /(.*)((?
100 32         129 $remaining_path = $2 . $remaining_path;
101 32         145 ( $plain_part = $1 ) =~ s|^/||;
102 32         127 ($filter, $remaining_path) = extract_codeblock($remaining_path, "[]");
103 32         12088 $filter_already_extracted = 1;
104             } else {
105 1331         3359 $remaining_path = (chop $extracted) . $remaining_path;
106             }
107             }
108              
109 1806 100       9412 ($plain_part, $filter) = $extracted =~ m,^/ # leading /
110             (.*?) # path part
111             (\[.*\])?$ # optional filter
112             ,xsg unless $filter_already_extracted;
113 1806         3914 $plain_part = unescape $plain_part;
114             }
115              
116 12     12   104 no warnings 'uninitialized';
  12         27  
  12         4168  
117 1845 100 100     5914 if ($plain_part eq '') { $kind ||= ANYWHERE }
  310 100       1207  
    100          
    100          
    100          
    100          
118 304   100     1131 elsif ($plain_part eq '*') { $kind ||= ANYSTEP }
119 125   100     485 elsif ($plain_part eq '.') { $kind ||= NOSTEP }
120 114   100     373 elsif ($plain_part eq '..') { $kind ||= PARENT }
121 19   50     67 elsif ($plain_part eq '::ancestor') { $kind ||= ANCESTOR }
122 21   100     65 elsif ($plain_part eq '::ancestor-or-self') { $kind ||= ANCESTOR_OR_SELF }
123 952   100     2756 else { $kind ||= KEY }
124              
125 1845         9246 push @steps, Step->new->part($plain_part)->kind($kind)->filter($filter);
126             }
127 460 100       1287 pop @steps if $steps[-1]->kind eq ANYWHERE; # ignore final '/'
128 460         1459 $self->_steps( \@steps );
129             }
130              
131             sub match {
132 245     245 1 470 my ($self, $data) = @_;
133              
134 245         318 return @{$self->matchr($data)};
  245         463  
135             }
136              
137             sub matchr {
138 436     436 1 708 my ($self, $data) = @_;
139              
140 436         3015 my $context = Context
141             ->new
142             ->current_points([ Point->new->ref(\$data) ])
143             ->give_references($self->give_references);
144 436         1447 return $context->matchr($self);
145             }
146              
147             1;
148              
149             __END__