File Coverage

blib/lib/Data/ZPath/Node.pm
Criterion Covered Total %
statement 177 204 86.7
branch 117 170 68.8
condition 123 170 72.3
subroutine 28 29 96.5
pod 19 19 100.0
total 464 592 78.3


line stmt bran cond sub pod time code
1 9     9   347804 use strict;
  9         22  
  9         402  
2 9     9   67 use warnings;
  9         19  
  9         590  
3              
4             package Data::ZPath::Node;
5              
6 9     9   88 use B ();
  9         18  
  9         3381  
7 9     9   55 use Scalar::Util qw(blessed refaddr reftype isdual);
  9         37  
  9         37344  
8              
9             our $VERSION = '0.001000';
10              
11             sub _created_as_string {
12 2181     2181   3134 my $value = shift;
13 2181 50 33     7820 defined $value
      33        
14             and not ref $value
15             and not _is_bool( $value )
16             and not _created_as_number( $value );
17             }
18              
19             sub _created_as_number {
20 5518     5518   8041 my $value = shift;
21 5518 50       9828 return !!0 unless defined $value;
22 5518 50       10306 return !!0 if ref $value;
23 5518 100       12587 return !!0 if utf8::is_utf8( $value );
24 5434         16660 my $b_obj = B::svref_2object(\$value);
25 5434         11500 my $flags = $b_obj->FLAGS;
26 5434 100 100     19877 return !!1 if $flags & ( B::SVp_IOK() | B::SVp_NOK() ) and not( $flags & B::SVp_POK() );
27 4278         19156 return !!0;
28             }
29              
30             sub _is_bool {
31 5615     5615   8897 my $value = shift;
32              
33 5615   100     14450 my $ref = ref($value) || '';
34 5615 100 66     11328 if ( $ref eq 'SCALAR' and defined $$value ) {
35 1 50       5 return !!1 if $$value eq 0;
36 1 50       10 return !!1 if $$value eq 1;
37             }
38              
39 5614 50 66     13151 if ( blessed($value) and $value->isa('Types::Serialiser::Boolean') ) {
40 0         0 return !!1;
41             }
42              
43 5614 100 66     11170 if ( blessed($value) and $value->isa('JSON::PP::Boolean') ) {
44 96         504 return !!1;
45             }
46              
47 5518 50       10341 return !!0 unless defined $value;
48 5518 50       9609 return !!0 if $ref;
49 5518 100       21711 return !!0 unless isdual( $value );
50 36 0 33     51 return !!1 if $value and "$value" eq '1' and $value + 0 == 1;
      33        
51 36 50 33     84 return !!1 if not $value and "$value" eq q'' and $value + 0 == 0;
      33        
52 36         81 return !!0;
53             }
54              
55             sub from_root {
56 424     424 1 862593 my ( $class, $obj ) = @_;
57 424         1114 return $class->_wrap($obj);
58             }
59              
60             sub _wrap {
61 10174     10174   19939 my ( $class, $obj, $parent, $key, $ix ) = @_;
62              
63 10174   100     22589 my $is_xml = blessed($obj) && $obj->isa('XML::LibXML::Node');
64 10174         13159 my $id;
65              
66 10174 100 100     23722 if ( blessed($obj) && $obj->isa('XML::LibXML::Document') ) {
67 86         498 $obj = $obj->documentElement;
68 86         405 $key = $obj->nodeName;
69 86         180 $is_xml = 1;
70             }
71              
72 10174 100       21293 if ( $is_xml ) {
    100          
    100          
73 3565         6526 $id = 'xml:' . refaddr($obj);
74             } elsif ( ref($obj) ) {
75 1806         3907 $id = 'ref:' . refaddr($obj);
76             } elsif ( $parent ) {
77 3019         5755 my $pid = $parent->id;
78 3019 50       7960 $pid = 'root' unless defined $pid;
79 3019 50       4979 my $k = defined $key ? $key : '';
80 3019         6198 $id = 'slot:' . $pid . ':' . $k;
81             } else {
82             # primitive: no stable identity, but used as a value node (not deduped as a tree node)
83 1784         2609 $id = undef;
84             }
85              
86 10174         57450 return bless {
87             raw => $obj,
88             parent => $parent,
89             key => $key,
90             id => $id,
91             ix => $ix,
92             slot => undef, # coderef getter/setter for Perl scalar lvalue
93             }, $class;
94             }
95              
96 4643     4643 1 8783 sub raw { $_[0]{raw} }
97 197     197 1 483 sub parent { $_[0]{parent} }
98 45     45 1 114 sub key { $_[0]{key} }
99 7220     7220 1 13458 sub id { $_[0]{id} }
100 28     28 1 79 sub ix { $_[0]{ix} }
101 3     3 1 26 sub index { $_[0]{ix} }
102              
103             sub slot {
104 5     5 1 16 my ( $self ) = @_;
105 5         21 return $self->{slot};
106             }
107              
108             sub with_slot {
109 3020     3020 1 5590 my ( $self, $slot ) = @_;
110 3020         4886 $self->{slot} = $slot;
111 3020         4510 return $self;
112             }
113              
114             sub type {
115 8380     8380 1 13758 my ( $self, $x ) = @_;
116 8380 100       20413 $x = $self->{raw} if @_ == 1;
117              
118 8380 100 100     22203 if ( blessed($x) && $x->isa('CBOR::Free::Tagged') ) {
119 2         15 return $self->type($x->[1]);
120             }
121              
122 8378 50 66     25454 if ( blessed($x) && $x->isa('XML::LibXML::Namespace') ) {
123 0         0 return 'attr';
124             }
125 8378 100 100     21461 if ( blessed($x) && $x->isa('XML::LibXML::Attr') ) {
126 27         49 return 'attr';
127             }
128 8351 100 100     20586 if ( blessed($x) && $x->isa('XML::LibXML::Text') ) {
129 1278         3534 return 'text';
130             }
131 7073 100 100     15732 if ( blessed($x) && $x->isa('XML::LibXML::Element') ) {
132 2137         5130 return 'element';
133             }
134 4936 50 66     10055 if ( blessed($x) && $x->isa('XML::LibXML::Document') ) {
135 0         0 return 'document';
136             }
137 4936 50 66     9833 if ( blessed($x) && $x->isa('XML::LibXML::Comment') ) {
138 0         0 return 'comment';
139             }
140              
141 4936 50 66     12938 if ( blessed($x) && $x->isa('Math::BigInt') ) {
142 0         0 return 'number';
143             }
144              
145 4936 100       9033 return 'null' unless defined $x;
146 4835 100       12242 return 'map' if ref($x) eq 'HASH';
147 3963 100       8411 return 'list' if ref($x) eq 'ARRAY';
148 3434 100       5913 return 'boolean' if _is_bool($x);
149 3337 100       6271 return 'number' if _created_as_number($x);
150 2181 50       4010 return 'string' if _created_as_string($x);
151 0         0 return ref($x);
152             }
153              
154             # Essentially returns raw, but normalizes booleans
155             sub value {
156 459     459 1 8244 my ( $self, $x ) = @_;
157 459 50       1398 $x = $self->{raw} if @_ == 1;
158              
159 459 100 66     2005 if ( ref $x and reftype($x) and reftype($x) eq 'SCALAR' ) {
      100        
160 103 100 66     480 return !!$$x if $x eq 0 || $x eq 1;
161             }
162 458 50 66     2685 if ( blessed($x) and $x->isa('Types::Serialiser::Boolean') ) {
163 0 0       0 return !!( $x ? 1 : 0 );
164             }
165 458 50 66     1224 if ( blessed($x) and $x->isa('JSON::PP::Boolean') ) {
166 0 0       0 return !!( $x ? 1 : 0 );
167             }
168              
169 458         1285 return $x;
170             }
171              
172             sub primitive_value {
173 1296     1296 1 2092 my ( $self, $x ) = @_;
174 1296 50       2859 $x = $self->{raw} if @_ == 1;
175              
176 1296 50 66     2882 if ( blessed($x) && $x->isa('CBOR::Free::Tagged') ) {
177 0         0 return $self->type($x->[1]);
178             }
179              
180 1296 50 66     2500 if ( blessed($x) && $x->isa('XML::LibXML::Document') ) {
181 0         0 my $de = $x->documentElement;
182 0 0       0 return defined($de) ? $de->textContent : undef;
183             }
184 1296 50 66     2460 if ( blessed($x) && $x->isa('XML::LibXML::Namespace') ) {
185 0   0     0 return $x->declaredURI // $x->nodeValue // '';
      0        
186             }
187 1296 100 100     2395 if ( blessed($x) && $x->isa('XML::LibXML::Attr') ) {
188 4         18 return $x->getValue;
189             }
190 1292 100 100     2541 if ( blessed($x) && $x->isa('XML::LibXML::Element') ) {
191 12         81 return $x->textContent;
192             }
193 1280 50 66     2399 if ( blessed($x) && $x->isa('XML::LibXML::Text') ) {
194 0         0 return $x->data;
195             }
196 1280 50 66     5910 if ( blessed($x) && $x->isa('XML::LibXML::Comment') ) {
197 0         0 return $x->data;
198             }
199              
200 1280 100 66     2390 if ( ref $x and reftype($x) and reftype($x) eq 'SCALAR' ) {
      100        
201 1 50 33     7 return !!$$x if $x eq 0 || $x eq 1;
202             }
203              
204 1280 50 66     2524 if ( blessed($x) and $x->isa('Types::Serialiser::Boolean') ) {
205 0 0       0 return !!( $x ? 1 : 0 );
206             }
207              
208 1280 50 66     2382 if ( blessed($x) and $x->isa('JSON::PP::Boolean') ) {
209 0 0       0 return !!( $x ? 1 : 0 );
210             }
211              
212 1280         2839 return $x;
213             }
214              
215             sub string_value {
216 465     465 1 9231 my ( $self, $x ) = @_;
217 465 50       1119 $x = $self->{raw} if @_ == 1;
218              
219 465 50 66     1286 if ( blessed($x) && $x->isa('CBOR::Free::Tagged') ) {
220 0         0 return $self->type($x->[1]);
221             }
222              
223 465 50 66     1213 if ( blessed($x) && $x->isa('XML::LibXML::Document') ) {
224 0         0 my $de = $x->documentElement;
225 0 0       0 return defined($de) ? $de->textContent : undef;
226             }
227 465 100 100     1131 if ( blessed($x) && $x->isa('XML::LibXML::Namespace') ) {
228 1   33     5 return $x->declaredURI // $x->nodeValue // '';
      50        
229             }
230 464 100 100     1089 if ( blessed($x) && $x->isa('XML::LibXML::Attr') ) {
231 48         255 return $x->getValue;
232             }
233 416 100 100     950 if ( blessed($x) && $x->isa('XML::LibXML::Element') ) {
234 73         1336 return $x->textContent;
235             }
236 343 100 100     651 if ( blessed($x) && $x->isa('XML::LibXML::Text') ) {
237 10         105 return $x->data;
238             }
239 333 50 66     689 if ( blessed($x) && $x->isa('XML::LibXML::Comment') ) {
240 0         0 return $x->data;
241             }
242              
243 333         659 my $v = $self->primitive_value;
244 333 100       737 return undef unless defined $v;
245 332         919 return "$v";
246             }
247              
248             sub number_value {
249 263     263 1 419 my ( $self ) = @_;
250 263         487 my $v = $self->primitive_value;
251 263 100 66     1141 return undef unless defined $v && Scalar::Util::looks_like_number($v);
252              
253 262 100 100     1183 if ( $Data::ZPath::UseBigInt and $v =~ /\A-?[0-9]{19,}\z/ ) {
254 10         4969 require Math::BigInt;
255 10         124291 return Math::BigInt->from_dec($v);
256             }
257              
258 252         620 return 0 + $v;
259             }
260              
261             sub children {
262 6790     6790 1 11033 my ( $self ) = @_;
263 6790         12222 my $x = $self->{raw};
264              
265             # XML document: treat documentElement as child
266 6790 50 66     23440 if ( blessed($x) && $x->isa('XML::LibXML::Document') ) {
267 0         0 my $de = $x->documentElement;
268 0 0       0 return unless $de;
269 0         0 return Data::ZPath::Node->_wrap($de, $self, 0);
270             }
271              
272 6790 100 100     15782 if ( blessed($x) && $x->isa('XML::LibXML::Element') ) {
273 1975 50       8501 my @kids = $Data::ZPath::XmlIgnoreWS ? $x->nonBlankChildNodes : $x->childNodes;
274 1975         14638 my %count;
275 1975   100     2777 return map { Data::ZPath::Node->_wrap($_, $self, $_->nodeName, $count{$_->nodeName}++ || 0) } @kids;
  3410         17360  
276             }
277              
278 4815 100       9035 if ( ref($x) eq 'HASH' ) {
279 831         1204 my @out;
280 831         3047 for my $k (keys %$x) {
281 3240         7617 my $child = Data::ZPath::Node->_wrap($x->{$k}, $self, $k);
282             $child->with_slot(sub {
283 5 100   5   15 if ( @_ ) { $x->{$k} = $_[0]; }
  3         8  
284 5         38 return $x->{$k};
285 3240 100       16784 }) unless ref($x->{$k});
286 3240         7398 push @out, $child;
287             }
288 831         3492 return @out;
289             }
290              
291 3984 100       8646 if ( ref($x) eq 'ARRAY' ) {
292 403         593 my @out;
293 403         1128 for ( my $i = 0; $i < @$x; $i++ ) {
294 1272         2978 my $child = Data::ZPath::Node->_wrap($x->[$i], $self, $i, $i);
295             $child->with_slot(sub {
296 0 0   0   0 if ( @_ ) { $x->[$i] = $_[0]; }
  0         0  
297 0         0 return $x->[$i];
298 1272 100       7563 }) unless ref($x->[$i]);
299 1272         3026 push @out, $child;
300             }
301 403         1500 return @out;
302             }
303              
304 3581         6481 return ();
305             }
306              
307             sub attributes {
308 93     93 1 137 my ( $self ) = @_;
309 93         7179 my $x = $self->{raw};
310 93 100 100     277 return unless blessed($x) && $x->isa('XML::LibXML::Element');
311 54         101 my @attrs = $x->attributes;
312 54         266 return map { Data::ZPath::Node->_wrap($_, $self, '@' . $_->nodeName) } @attrs;
  28         84  
313             }
314              
315             sub name {
316 8534     8534 1 12417 my ( $self ) = @_;
317 8534         12234 my $x = $self->{raw};
318              
319 8534 50 66     20437 if ( blessed($x) && $x->isa('XML::LibXML::Attr') ) { return '@' . $x->nodeName; }
  0         0  
320 8534 100 100     19356 if ( blessed($x) && $x->isa('XML::LibXML::Element') ) { return $x->nodeName; }
  2109         8279  
321 6425 100 100     12970 if ( blessed($x) && $x->isa('XML::LibXML::Text') ) { return '#text'; }
  1062         4076  
322              
323 5363         29046 return $self->{key};
324             }
325              
326             sub dump {
327 2     2 1 10 my ( $self ) = @_;
328             return {
329 2         9 '@type' => $self->type,
330             '@id' => $self->id,
331             '@key' => $self->key,
332             '@index' => $self->index,
333             '@value' => $self->primitive_value,
334             children => [ map $_->dump, $self->children ],
335             attributes => [ map $_->dump, $self->attributes ],
336             };
337             }
338              
339             sub find {
340 5     5 1 518 require Data::ZPath;
341 5         19 my ( $self, $zpath ) = @_;
342 5 100       24 $zpath = Data::ZPath->new( $zpath ) unless blessed($zpath);
343 5         22 return $zpath->evaluate( $self );
344             }
345              
346             1;
347              
348             __END__