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.57';
5 11     11   759 use strict;
  11         29  
  11         381  
6 11     11   69 use warnings;
  11         28  
  11         329  
7              
8 11     11   4598 use Data::Dumper;
  11         76512  
  11         1061  
9 11     11   3842 use aliased 'Data::DPath::Step';
  11         8870  
  11         89  
10 11     11   1304 use aliased 'Data::DPath::Point';
  11         28  
  11         87  
11 11     11   1158 use aliased 'Data::DPath::Context';
  11         35  
  11         62  
12 11     11   7303 use Text::Balanced 2.02 'extract_delimited', 'extract_codeblock';
  11         170697  
  11         1353  
13              
14             use Class::XSAccessor
15 11         188 chained => 1,
16             accessors => {
17             path => 'path',
18             _steps => '_steps',
19             give_references => 'give_references',
20 11     11   142 };
  11         59  
21              
22 11         8065 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 11     11   4454 };
  11         32  
31              
32             sub new {
33 457     457 1 39603 my $class = shift;
34 457         2079 my $self = bless { @_ }, $class;
35 457         2094 $self->_build__steps;
36 457         5139 return $self;
37             }
38              
39             sub unescape {
40 1839     1839 1 4160 my ($str) = @_;
41              
42 1839 50       4700 return unless defined $str;
43 1839         4310 $str =~ s/(?
44 1839         3402 $str =~ s/\\{2}/\\/g;
45 1839         5011 return $str;
46             }
47              
48             sub unquote {
49 39     39 1 117 my ($str) = @_;
50 39         305 $str =~ s/^"(.*)"$/$1/g;
51 39         161 return $str;
52             }
53              
54 1839     1839 1 8472 sub quoted { shift =~ m,^/["'],; } # "
55              
56 11     11   146 eval 'use overload "~~" => \&op_match' if $] >= 5.010;
  11         35  
  11         86  
57              
58             sub op_match {
59 155     155 1 614 my ($self, $data, $rhs) = @_;
60              
61 155         584 return $self->matchr( $data );
62             }
63              
64             # essentially the Path parser
65             sub _build__steps {
66 457     457   1181 my ($self) = @_;
67              
68 457         1858 my $remaining_path = $self->path;
69 457         1194 my $extracted;
70             my @steps;
71              
72 457         4341 push @steps, Step->new->part('')->kind(ROOT);
73              
74 457         1810 while ($remaining_path) {
75 1839         5438 my $plain_part;
76             my $filter;
77 1839         0 my $kind;
78 1839 100       4385 if ( quoted($remaining_path) ) {
79 39         156 ($plain_part, $remaining_path) = extract_delimited($remaining_path, q/'"/, "/"); # '
80 39         5565 ($filter, $remaining_path) = extract_codeblock($remaining_path, "[]");
81 39         6575 $plain_part = unescape unquote $plain_part;
82 39         105 $kind = KEY; # quoted is always a key
83             }
84             else
85             {
86 1800         3553 my $filter_already_extracted = 0;
87 1800         5933 ($extracted, $remaining_path) = extract_delimited($remaining_path,'/');
88              
89 1800 100       156515 if (not $extracted) {
90 440         1386 ($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 1360 100 100     6661 if ($extracted =~ /(.*)((?
100 32         174 $remaining_path = $2 . $remaining_path;
101 32         271 ( $plain_part = $1 ) =~ s|^/||;
102 32         250 ($filter, $remaining_path) = extract_codeblock($remaining_path, "[]");
103 32         18258 $filter_already_extracted = 1;
104             } else {
105 1328         4425 $remaining_path = (chop $extracted) . $remaining_path;
106             }
107             }
108              
109 1800 100       13014 ($plain_part, $filter) = $extracted =~ m,^/ # leading /
110             (.*?) # path part
111             (\[.*\])?$ # optional filter
112             ,xg unless $filter_already_extracted;
113 1800         5593 $plain_part = unescape $plain_part;
114             }
115              
116 11     11   143 no warnings 'uninitialized';
  11         36  
  11         3914  
117 1839 100 100     8249 if ($plain_part eq '') { $kind ||= ANYWHERE }
  310 100       2254  
    100          
    100          
    100          
    100          
118 304   100     1656 elsif ($plain_part eq '*') { $kind ||= ANYSTEP }
119 125   100     544 elsif ($plain_part eq '.') { $kind ||= NOSTEP }
120 114   100     499 elsif ($plain_part eq '..') { $kind ||= PARENT }
121 19   50     93 elsif ($plain_part eq '::ancestor') { $kind ||= ANCESTOR }
122 21   100     91 elsif ($plain_part eq '::ancestor-or-self') { $kind ||= ANCESTOR_OR_SELF }
123 946   100     3996 else { $kind ||= KEY }
124              
125 1839         13264 push @steps, Step->new->part($plain_part)->kind($kind)->filter($filter);
126             }
127 457 100       2088 pop @steps if $steps[-1]->kind eq ANYWHERE; # ignore final '/'
128 457         2238 $self->_steps( \@steps );
129             }
130              
131             sub match {
132 245     245 1 722 my ($self, $data) = @_;
133              
134 245         465 return @{$self->matchr($data)};
  245         759  
135             }
136              
137             sub matchr {
138 433     433 1 1197 my ($self, $data) = @_;
139              
140 433         5226 my $context = Context
141             ->new
142             ->current_points([ Point->new->ref(\$data) ])
143             ->give_references($self->give_references);
144 433         2157 return $context->matchr($self);
145             }
146              
147             1;
148              
149             __END__