File Coverage

blib/lib/Data/Merger.pm
Criterion Covered Total %
statement 54 61 88.5
branch 31 42 73.8
condition 15 24 62.5
subroutine 5 5 100.0
pod 0 4 0.0
total 105 136 77.2


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             #!/usr/bin/perl -d:ptkdb -w
3             #
4             # This module is based on code that was implemented
5             # when working for Newtec Cy, located in Belgium,
6             # http://www.newtec.be/.
7             #
8              
9             =head1 NAME
10              
11             Data::Merger - merge nested Perl data structures.
12              
13             =head1 SYNOPSIS
14              
15             use Data::Merger qw(merger);
16              
17             my $target
18             = {
19             a => 2,
20             e => {
21             e1 => {
22             },
23             },
24             };
25              
26             my $source
27             = {
28             a => 1,
29             e => {
30             e2 => {
31             },
32             e3 => {
33             },
34             },
35             };
36              
37             my $expected_data
38             = {
39             a => 1,
40             e => {
41             e1 => {
42             },
43             e2 => {
44             },
45             e3 => {
46             },
47             },
48             };
49              
50             my $merged_data = merger($target, $source);
51              
52             use Data::Comparator qw(data_comparator);
53              
54             my $differences = data_comparator($merged_data, $expected_data);
55              
56             if ($differences->is_empty())
57             {
58             print "$0: 3: success\n";
59              
60             ok(1, '3: success');
61             }
62             else
63             {
64             print "$0: 3: failed\n";
65              
66             ok(0, '3: failed');
67             }
68              
69             =head1 DESCRIPTION
70              
71             Data::Merger contains subs to merge two nested perl data structures,
72             overwriting values where appropriate. For scalars, default is to
73             overwrite values. The two data structure can contain perl hashes,
74             arrays and scalars, and should have the same overall structure (unless
75             otherwise specified using options, see below). They should not be
76             self-referential.
77              
78             This module implements the functions merger(), merger_any(),
79             merger_array() and merger_hash(). The main entry point is merger().
80              
81             The merger() function is called with three arguments:
82              
83             =over 2
84              
85             =item target and source arguments
86              
87             are the two data structures to be merged. The target data structure
88             will be overwritten, and results are copied by reference. If you need
89             plain copies, first Clone(3) your original data.
90              
91             =item options
92              
93             Options is a hash reference. There is currently one option:
94              
95             =over 2
96              
97             =item {arrays}->{overwrite}
98              
99             If this value evals to 1, array entries are always overwritten,
100             regardless of type / structure mismatches of the content of the
101             entries in the arrays.
102              
103             =item {hashes}->{overwrite}
104              
105             If this value evals to 1, hash entries are always overwritten,
106             regardless of type / structure mismatches of the values of the tuples
107             in the hashes.
108              
109             =back
110              
111             =back
112              
113             =head1 BUGS
114              
115             Does only work with scalars, hashes and arrays. Support for
116             self-referential structures seems broken at the moment.
117              
118             This works for me to overwrite configuration defaults with specific
119             values. Yet, is certainly incomplete.
120              
121             =head1 AUTHOR
122              
123             Hugo Cornelis, hugo.cornelis@gmail.com
124              
125             Copyright 2007 Hugo Cornelis.
126              
127             This module is free software; you can redistribute it and/or
128             modify it under the same terms as Perl itself.
129              
130             =head1 SEE ALSO
131              
132             Data::Transformator(3), Data::Comparator(3), Clone(3)
133              
134             =cut
135              
136              
137             package Data::Merger;
138              
139              
140 3     3   1907 use strict;
  3         4  
  3         2640  
141              
142              
143             our @ISA = qw(Exporter);
144              
145             our @EXPORT_OK = qw(
146             merger
147             );
148              
149              
150             #
151             # subs to merge two datastructures.
152             #
153              
154             sub merger_any
155             {
156 22     22 0 25 my $contents = shift;
157              
158 22         27 my $data = shift;
159              
160 22         26 my $options = shift;
161              
162             # simply check what kind of data structure we are dealing
163             # with and forward to the right sub.
164              
165 22         35 my $type = ref $contents;
166              
167 22 100       65 if ($type eq 'HASH')
    50          
168             {
169 12         33 merger_hash($contents, $data, $options);
170             }
171             elsif ($type eq 'ARRAY')
172             {
173 10         23 merger_array($contents, $data, $options);
174             }
175             else
176             {
177 0         0 die "$0: *** Error: Data::Merger error: merger_any() encounters an unknown data type $type";
178             }
179             }
180              
181              
182             sub merger_hash
183             {
184 12     12 0 958 my $contents = shift;
185              
186 12         15 my $data = shift;
187              
188 12         21 my $options = shift;
189              
190 12 100       39 if (!exists $options->{hashes}->{overwrite})
191             {
192 8         18 $options->{hashes}->{overwrite} = 1;
193             }
194              
195             # loop over all values in the contents hash.
196              
197 12         198 foreach my $section (keys %$data)
198             {
199 23 50 66     88 if (exists $contents->{$section}
200             || $options->{hashes}->{overwrite} eq 1)
201             {
202 23         35 my $value = $data->{$section};
203              
204 23         37 my $contents_type = ref $contents->{$section};
205 23         31 my $value_type = ref $value;
206              
207 23 100 66     70 if (!defined $value
208             && $options->{undefined}->{overwrite} ne 1)
209             {
210 2         6 next;
211             }
212              
213 21 100 66     996 if ($contents_type && $value_type)
    100 66        
    50          
214             {
215 9 100       39 if ($contents_type eq $value_type)
    100          
    50          
216             {
217             # two references of the same type, go one
218             # level deeper.
219              
220 3         22 merger_any($contents->{$section}, $value, $options);
221             }
222             elsif ($options->{hashes}->{overwrite} eq 1)
223             {
224             # copy value regardless of type
225              
226 5         19 $contents->{$section} = $value;
227             }
228             elsif ($options->{hashes}->{overwrite} eq 0)
229             {
230             # keep old value
231              
232             }
233             else
234             {
235 0         0 die "$0: *** Error: Data::Merger error: contents_type is '$contents_type' and does not match with value_type $value_type";
236             }
237             }
238             elsif (!$contents_type && !$value_type)
239             {
240             # copy scalar value
241              
242 9         29 $contents->{$section} = $value;
243             }
244             elsif ($options->{hashes}->{overwrite} eq 1)
245             {
246             # copy value regardless of type
247              
248 3         193 $contents->{$section} = $value;
249             }
250             else
251             {
252 0         0 die "$0: *** Error: Data::Merger error: contents_type is '$contents_type' and does not match with value_type $value_type";
253             }
254             }
255             else
256             {
257             #t could be a new key being added.
258             }
259             }
260             }
261              
262              
263             sub merger_array
264             {
265 10     10 0 13 my $contents = shift;
266              
267 10         10 my $data = shift;
268              
269 10         12 my $options = shift;
270              
271 10 100       31 if (!exists $options->{arrays}->{overwrite})
272             {
273 9         20 $options->{arrays}->{overwrite} = 1;
274             }
275              
276             # loop over all values in the contents array.
277              
278 10         12 my $count = 0;
279              
280 10         18 foreach my $section (@$data)
281             {
282 22 50 66     67 if (exists $contents->[$count]
283             || $options->{arrays}->{overwrite} eq 1)
284             {
285 22         29 my $value = $data->[$count];
286              
287 22         36 my $contents_type = ref $contents->[$count];
288 22         26 my $value_type = ref $value;
289              
290 22 100 66     86 if (!defined $value
291             && $options->{undefined}->{overwrite} ne 1)
292             {
293 12         14 $count++;
294              
295 12         25 next;
296             }
297              
298 10 100 66     71 if ($contents_type && $value_type)
    50 33        
    0          
299             {
300 4 50       25 if ($contents_type eq $value_type)
    100          
    50          
301             {
302             # two references of the same type, go one
303             # level deeper.
304              
305 0         0 merger_any($contents->[$count], $value, $options);
306             }
307             elsif ($options->{arrays}->{overwrite} eq 1)
308             {
309             # overwrite array content
310              
311 3         7 $contents->[$count] = $value;
312             }
313             elsif ($options->{arrays}->{overwrite} eq 0)
314             {
315             # keep old value
316              
317             }
318             else
319             {
320 0         0 die "$0: *** Error: Data::Merger error: contents_type is '$contents_type' and does not match with value_type $value_type";
321             }
322             }
323             elsif (!$contents_type && !$value_type)
324             {
325             # copy scalar value
326              
327 6         12 $contents->[$count] = $value;
328             }
329             elsif ($options->{arrays}->{overwrite} eq 1)
330             {
331             # overwrite array content
332              
333 0         0 $contents->[$count] = $value;
334             }
335             else
336             {
337 0         0 die "$0: *** Error: Data::Merger error: contents_type is '$contents_type' and does not match with value_type $value_type";
338             }
339             }
340             else
341             {
342             #t could be a new key being added.
343             }
344              
345 10         29 $count++;
346             }
347             }
348              
349              
350             sub merger
351             {
352 19     19 0 6451 my $target = shift;
353              
354 19         27 my $source = shift;
355              
356 19         26 my $options = shift;
357              
358 19 50       66 if (!exists $options->{undefined}->{overwrite})
359             {
360 19         43 $options->{undefined}->{overwrite} = 0;
361             }
362              
363             #t I don't think the todos below are still valid, the idea is
364             #t sound though:
365              
366             #t Should actually use a simple iterator over the detransformed data
367             #t that keeps track of examined paths. Then use the path to store
368             #t encountered value in the original data.
369              
370             #t Note that the iterator is partly implemented in Sesa::Transform and
371             #t Sesa::TreeDocument. A further abstraction could be useful.
372              
373             # first inductive step : merge all data.
374              
375 19         39 merger_any($target, $source, $options);
376              
377 19         236 return $target;
378             }
379              
380              
381             1;
382              
383