line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#=============================================== |
2
|
|
|
|
|
|
|
package Banal::Utils::Data; |
3
|
|
|
|
|
|
|
|
4
|
1
|
|
|
1
|
|
11369
|
use 5.006; |
|
1
|
|
|
|
|
9
|
|
|
1
|
|
|
|
|
109
|
|
5
|
1
|
|
|
1
|
|
7
|
use utf8; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
25
|
|
6
|
1
|
|
|
1
|
|
103
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
56
|
|
7
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
43
|
|
8
|
1
|
|
|
1
|
|
4
|
no warnings qw(uninitialized); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
116
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
require Exporter; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our @ISA = qw(Exporter); |
13
|
|
|
|
|
|
|
our @EXPORT_OK = qw( banal_get_data |
14
|
|
|
|
|
|
|
flatten_complex_data_to_list |
15
|
|
|
|
|
|
|
flatten_complex_data_to_list_with_options |
16
|
|
|
|
|
|
|
); |
17
|
1
|
|
|
1
|
|
7
|
use Carp; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
115
|
|
18
|
1
|
|
|
1
|
|
3483
|
use Data::Dumper; |
|
1
|
|
|
|
|
13060
|
|
|
1
|
|
|
|
|
88
|
|
19
|
1
|
|
|
1
|
|
9
|
use Banal::Utils::String qw(trim); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
67
|
|
20
|
1
|
|
|
1
|
|
6
|
use Banal::Utils::Array qw(array1_starts_with_array2); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
931
|
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
##############################################################################" |
26
|
|
|
|
|
|
|
# PUBLIC (exportable) FUNCTIONS |
27
|
|
|
|
|
|
|
##############################################################################" |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
#---------------------------------- |
31
|
|
|
|
|
|
|
# Function, not a method! |
32
|
|
|
|
|
|
|
#---------------------------------- |
33
|
|
|
|
|
|
|
sub banal_get_data { |
34
|
0
|
|
|
0
|
1
|
|
my $args = {@_}; |
35
|
0
|
|
0
|
|
|
|
my $opts = $args->{options} || {}; |
36
|
0
|
|
|
|
|
|
my $search_upwards_while_not_defined = $opts->{search_upwards_while_not_defined}; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# This is where the MAGIC happens. For a full list of options, see the function "normalize_data_root_and_keys()". |
41
|
0
|
|
|
|
|
|
my ($root, @keys) = _normalize_data_root_and_keys (@_); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# The data root should have been defined by now. |
44
|
0
|
0
|
|
|
|
|
return unless ($root); |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# All this for nothing? |
47
|
0
|
0
|
|
|
|
|
return $root if (scalar(@keys) < 1); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# The reason for the below loop is to allow outer level 'variables' to be used when the variable is not defined at the proper (inner) level. |
50
|
|
|
|
|
|
|
# Very handy for CONFIGURATION handling scenarios. |
51
|
0
|
|
|
|
|
|
my $key = pop @keys; |
52
|
0
|
|
|
|
|
|
while (scalar(@keys) >= 0) { |
53
|
0
|
|
|
|
|
|
my $value= _banal_basic_get_data_via_key_list(data=>$root, keys=>[@keys, $key]); |
54
|
0
|
0
|
|
|
|
|
return $value if defined($value); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# Continue searching upwards if we are allowed to do so. Return otherwise. |
57
|
0
|
0
|
|
|
|
|
return unless $search_upwards_while_not_defined; |
58
|
|
|
|
|
|
|
|
59
|
0
|
|
|
|
|
|
pop @keys; |
60
|
|
|
|
|
|
|
} |
61
|
0
|
|
|
|
|
|
return; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
#----------------------------------------------- |
67
|
|
|
|
|
|
|
# FUNCTION (not a method). |
68
|
|
|
|
|
|
|
#----------------------------------------------- |
69
|
|
|
|
|
|
|
sub flatten_complex_data_to_list { |
70
|
0
|
|
|
0
|
1
|
|
return flatten_complex_data_to_list_with_options (data=>[@_], on_ArrayRef=>'flatten', on_HashRef=>'flatten', on_ScalarRef=>'flatten'); |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
#----------------------------------------------- |
75
|
|
|
|
|
|
|
# FUNCTION (not a method). |
76
|
|
|
|
|
|
|
#----------------------------------------------- |
77
|
|
|
|
|
|
|
sub flatten_complex_data_to_list_with_options { |
78
|
0
|
|
|
0
|
1
|
|
my $opts = {@_}; |
79
|
0
|
|
|
|
|
|
my $data = $opts->{data}; |
80
|
0
|
|
0
|
|
|
|
my $on_ArrayRef = $opts->{on_ArrayRef} || 'flatten'; |
81
|
0
|
|
0
|
|
|
|
my $on_HashRef = $opts->{on_HashRef} || 'flatten'; |
82
|
0
|
|
0
|
|
|
|
my $on_ScalarRef = $opts->{on_ScalarRef} || 'flatten'; |
83
|
0
|
|
|
|
|
|
my @list = (); |
84
|
|
|
|
|
|
|
|
85
|
0
|
|
|
|
|
|
foreach my $datum (@$data) { |
86
|
0
|
0
|
0
|
|
|
|
if ((reftype($datum) eq 'ARRAY') && ($on_ArrayRef =~ /^flatten|dereference$/io)){ |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
87
|
0
|
|
|
|
|
|
push @list, flatten_complex_data_to_list_with_options(data=>$datum); |
88
|
0
|
|
|
|
|
|
next; |
89
|
|
|
|
|
|
|
}elsif ((reftype($datum) eq 'HASH') && ($on_HashRef =~ /^flatten|dereference$/io)){ |
90
|
0
|
|
|
|
|
|
push @list, flatten_complex_data_to_list_with_options(data=>[%$datum]); |
91
|
0
|
|
|
|
|
|
next; |
92
|
|
|
|
|
|
|
}elsif ((reftype($datum) eq 'SCALAR') && ($on_ScalarRef =~ /^flatten|dereference$/io)){ |
93
|
0
|
|
|
|
|
|
push @list, flatten_complex_data_to_list_with_options(data=>[$$datum]);; |
94
|
0
|
|
|
|
|
|
next; |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
}else { |
97
|
0
|
|
|
|
|
|
push @list, $datum; |
98
|
0
|
|
|
|
|
|
next; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
} |
102
|
0
|
|
|
|
|
|
return @list; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
#******************************************************************* |
107
|
|
|
|
|
|
|
# PRIVATE (non-exported) FUNCTIONS |
108
|
|
|
|
|
|
|
#******************************************************************* |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
#---------------------------------- |
111
|
|
|
|
|
|
|
sub _is_absolute_data_key_reference { |
112
|
0
|
|
0
|
0
|
|
|
return ((scalar(@_) > 0) && !$_[0]); |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
#---------------------------------- |
116
|
|
|
|
|
|
|
sub _normalize_data_root_and_keys { |
117
|
0
|
|
|
0
|
|
|
my $args = {@_}; |
118
|
0
|
|
0
|
|
|
|
my $keys = $args->{keys} || $args->{key} || $args->{path} || []; |
119
|
0
|
|
|
|
|
|
my $data = $args->{data}; |
120
|
0
|
|
0
|
|
|
|
my $context = $args->{context} || []; |
121
|
0
|
|
0
|
|
|
|
my $opts = $args->{options} || {}; |
122
|
0
|
|
0
|
|
|
|
my $separator = $opts->{path_separator} || $opts->{separator} || '/'; |
123
|
0
|
0
|
|
|
|
|
my $remove_extra_separators = defined($opts->{remove_extra_separators}) ? $opts->{remove_extra_separators} : 1; |
124
|
0
|
0
|
|
|
|
|
my $remove_leading_separator = defined($opts->{remove_leading_separator}) ? $opts->{remove_leading_separator} : 0; |
125
|
0
|
0
|
|
|
|
|
my $remove_trailing_separator = defined($opts->{remove_trailing_separator}) ? $opts->{remove_trailing_separator} : $remove_extra_separators; |
126
|
0
|
0
|
|
|
|
|
my $remove_empty_segments = defined($opts->{remove_empty_segments}) ? $opts->{remove_empty_segments} : 0; |
127
|
0
|
0
|
|
|
|
|
my $try_avoiding_repeated_segments = defined($opts->{try_avoiding_repeated_segments}) ? $opts->{try_avoiding_repeated_segments} : 0; |
128
|
0
|
|
0
|
|
|
|
my $lc = $opts->{lower_case} || $opts->{lc} || 0; |
129
|
0
|
|
0
|
|
|
|
my $trim = $opts->{trim} || 0; |
130
|
|
|
|
|
|
|
|
131
|
0
|
|
|
|
|
|
my $mroot = undef; # Yeah, 'undef' by default. |
132
|
0
|
|
|
|
|
|
my $relevant_keys = []; |
133
|
0
|
|
|
|
|
|
my @accumulated_segs = (); |
134
|
0
|
|
|
|
|
|
my $use_path_semantics; |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
{ |
137
|
1
|
|
|
1
|
|
7
|
no warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
1129
|
|
|
0
|
|
|
|
|
|
|
138
|
0
|
0
|
0
|
|
|
|
$use_path_semantics = (defined($opts->{path}) && (($keys eq $opts->{path}) || ($keys == $opts->{path}))) ? 1 : $opts->{use_path_semantics}; |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
# Flatten all context and key segments (which are potentially a mix of path segment strings) |
142
|
0
|
|
|
|
|
|
$keys = flatten_complex_data_to_list_with_options(data=>[$data, $context, $keys], on_HashRef=>'keep'); |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# If we've got a HASH reference given as a key (or context) segment, that's our root. Otherwise, build the relevant thingy (relative to the root). |
145
|
0
|
|
|
|
|
|
foreach my $key (reverse @$keys) { |
146
|
0
|
0
|
|
|
|
|
if (reftype($key) eq 'HASH') { |
147
|
0
|
|
|
|
|
|
$mroot = $key; |
148
|
0
|
|
|
|
|
|
last; |
149
|
|
|
|
|
|
|
}else { |
150
|
0
|
|
|
|
|
|
unshift @$relevant_keys, $key; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
# Flatten all context and key segments (which are potentially a mix of path segment strings) |
155
|
0
|
|
|
|
|
|
while ( scalar(@$relevant_keys)) { |
156
|
0
|
|
|
|
|
|
my $key = pop @$relevant_keys; |
157
|
0
|
|
|
|
|
|
my @segs = flatten_complex_data_to_list_with_options(data=>[$key], on_HashRef=>'keep'); |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# If it's an empty ARRAY, just ignore, and pass on to the next one. |
160
|
0
|
0
|
|
|
|
|
next unless (scalar(@segs)); |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# Do we have much to do, anyway? |
163
|
0
|
0
|
|
|
|
|
if ($use_path_semantics) { |
164
|
0
|
|
|
|
|
|
my $path = join($separator, @segs); |
165
|
|
|
|
|
|
|
|
166
|
0
|
0
|
|
|
|
|
$path =~ s/${separator}+/${separator}/ if ($remove_extra_separators); |
167
|
0
|
0
|
|
|
|
|
$path =~ s/^${separator}// if ($remove_leading_separator); # If you ask for this, you won't be able to detect absolute paths. |
168
|
0
|
0
|
|
|
|
|
$path =~ s/${separator}$// if ($remove_trailing_separator); |
169
|
|
|
|
|
|
|
|
170
|
0
|
|
|
|
|
|
@segs = split /$separator/, $path; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# Lowercase and trim if required. |
174
|
0
|
0
|
|
|
|
|
@segs = [map {lc($_)} @segs] if ($lc); # Segments are all automatically lowercased, if asked for it. |
|
0
|
|
|
|
|
|
|
175
|
0
|
0
|
|
|
|
|
@segs = [map {trim($_)} @segs] if ($trim); # Segments are all automatically trimmed, if asked for. |
|
0
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
|
177
|
0
|
|
|
|
|
|
my @prepend_segs = @segs; |
178
|
0
|
0
|
|
|
|
|
if($try_avoiding_repeated_segments) { |
179
|
0
|
|
|
|
|
|
@prepend_segs = (); |
180
|
0
|
|
|
|
|
|
while (scalar(@segs)) { |
181
|
0
|
0
|
|
|
|
|
if (array1_starts_with_array2([@accumulated_segs], [@segs])) { |
182
|
0
|
|
|
|
|
|
last; |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
|
185
|
0
|
|
|
|
|
|
my $s = shift @segs; |
186
|
0
|
|
|
|
|
|
push @prepend_segs, $s; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
0
|
|
|
|
|
|
@accumulated_segs = (@prepend_segs, @accumulated_segs); |
191
|
|
|
|
|
|
|
|
192
|
0
|
0
|
|
|
|
|
last if (_is_absolute_data_key_reference(@accumulated_segs)); |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# Remove empty segments if we are asked for it. |
197
|
|
|
|
|
|
|
# If you ask for this, you won't be able to detect absolute paths later on (normally, we have already done the detection for you, though) |
198
|
0
|
0
|
|
|
|
|
@accumulated_segs = grep (/^\s*$/i, @accumulated_segs) if ($remove_empty_segments); |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# Here's a little something: We insert the root to the begining of the array. |
201
|
0
|
|
|
|
|
|
unshift @accumulated_segs, $mroot; |
202
|
|
|
|
|
|
|
|
203
|
0
|
0
|
|
|
|
|
return wantarray ? @accumulated_segs : [@accumulated_segs]; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
#---------------------------------- |
207
|
|
|
|
|
|
|
# Function, not a method! |
208
|
|
|
|
|
|
|
# Allows to get a data element within a deep structure composed of possibly complex data types (HASH, ARRAY, ...) |
209
|
|
|
|
|
|
|
# Example: |
210
|
|
|
|
|
|
|
# _banal_basic_get_data_via_key_list (data=>$h, keys=>["employee[23]", "department", "name"]) |
211
|
|
|
|
|
|
|
# |
212
|
|
|
|
|
|
|
# In this example, we are assuming that the initial data ($h) is a HASH that has a key called 'employee' which refers to an ARRAY of hashes, .... |
213
|
|
|
|
|
|
|
#---------------------------------- |
214
|
|
|
|
|
|
|
sub _banal_basic_get_data_via_key_list { |
215
|
0
|
|
|
0
|
|
|
my $args = {@_}; |
216
|
0
|
|
|
|
|
|
my $data = $args->{data}; |
217
|
0
|
|
|
|
|
|
my $keys = $args->{keys}; |
218
|
0
|
|
|
|
|
|
my @segments = @$keys; |
219
|
|
|
|
|
|
|
|
220
|
0
|
|
|
|
|
|
foreach my $segment (@segments) { |
221
|
0
|
0
|
|
|
|
|
next unless $segment; |
222
|
|
|
|
|
|
|
|
223
|
0
|
|
|
|
|
|
my $element = $segment; |
224
|
0
|
|
|
|
|
|
my $index; |
225
|
|
|
|
|
|
|
|
226
|
0
|
0
|
|
|
|
|
if($element =~ /^([^\[]*)\[(\d+)\]$/) { |
227
|
0
|
|
|
|
|
|
$element = $1; |
228
|
0
|
|
|
|
|
|
$index = $2; |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
else { |
231
|
0
|
|
|
|
|
|
$index = undef; |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# We're on a SCALAR. Fishy, since we have got a key segment, too. |
235
|
0
|
0
|
|
|
|
|
unless(reftype($data)) { |
236
|
0
|
|
|
|
|
|
return; |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# We're on a SCALAR Reference. Fishy, since we have got a key segment, too. |
240
|
0
|
0
|
|
|
|
|
if(reftype($data) eq "SCALAR") { |
241
|
0
|
|
|
|
|
|
return; |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# We're on an ARRAY. |
246
|
0
|
0
|
|
|
|
|
if(reftype($data) eq "ARRAY") { |
247
|
0
|
0
|
0
|
|
|
|
if (defined ($index) && !defined($element)) { |
|
|
0
|
|
|
|
|
|
248
|
0
|
0
|
|
|
|
|
if(exists $data->[$index]) { |
249
|
0
|
|
|
|
|
|
$data = $data->[$index]; |
250
|
0
|
|
|
|
|
|
next; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
else { |
253
|
0
|
|
|
|
|
|
croak "No element with index $index!\n"; |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
}elsif (!defined($element)) { |
256
|
0
|
|
|
|
|
|
return $data |
257
|
|
|
|
|
|
|
} |
258
|
0
|
|
|
|
|
|
return; |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
# We're on a HASH. |
263
|
0
|
0
|
|
|
|
|
if(reftype($data) eq "HASH") { |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# the entire segment (even if it matches the array indexing pattern!) |
266
|
0
|
0
|
|
|
|
|
if (exists $data->{$segment}) { |
267
|
0
|
|
|
|
|
|
$data = $data->{$segment}; # this way, we are able to retreive weird hash values with keys that actually match our array indexing. |
268
|
0
|
|
|
|
|
|
next; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# Now, we are on the normal route. |
272
|
0
|
0
|
|
|
|
|
if (! exists $data->{$element}) { |
273
|
0
|
|
|
|
|
|
return; |
274
|
|
|
|
|
|
|
} |
275
|
0
|
0
|
|
|
|
|
if(reftype($data->{$element}) eq "ARRAY") { |
276
|
0
|
0
|
|
|
|
|
if(! defined($index) ) { |
277
|
|
|
|
|
|
|
#croak "$element is an array but you didn't specify an index to access it!\n"; |
278
|
0
|
|
|
|
|
|
$data = $data->{$element}; |
279
|
0
|
|
|
|
|
|
next; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
else { |
282
|
0
|
0
|
|
|
|
|
if(exists $data->{$element}->[$index]) { |
283
|
0
|
|
|
|
|
|
$data = $data->{$element}->[$index]; |
284
|
0
|
|
|
|
|
|
next; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
else { |
287
|
0
|
|
|
|
|
|
croak "$element doesn't have an element with index $index!\n"; |
288
|
0
|
|
|
|
|
|
return; |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
else { |
293
|
0
|
|
|
|
|
|
$data = $data->{$element}; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
|
return $data; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
1; |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
__END__ |