File Coverage

blib/lib/Data/Comparator.pm
Criterion Covered Total %
statement 64 66 96.9
branch 27 30 90.0
condition 19 28 67.8
subroutine 8 8 100.0
pod 0 5 0.0
total 118 137 86.1


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             #!/usr/bin/perl -d:ptkdb -w
3             #
4             # This is module is based on a module with the same name, implemented
5             # when working for Newtec Cy, located in Belgium,
6             # http://www.newtec.be/.
7             #
8              
9             package Data::Comparator;
10              
11              
12             #
13             # The main entry point for this module is the sub data_comparator().
14             # It compares two sets of (structured) data and reports on the
15             # differences found with a differences describing data structure.
16             #
17             # The algorithm used is of a subtractive kind. It subtracts the first
18             # data structure given from the second one. This means that, since it
19             # not possible to subtract what is not yet there, not all differences
20             # are reported. To have a report of all differences between
21             # structures A and B, first subtract A from B, next subtract B from A.
22             # The two result sets are an exact description of the differences
23             # between A and B (or should be, untested for the moment).
24             #
25              
26              
27 7     7   157841 use strict;
  7         20  
  7         273  
28              
29              
30 7     7   5141 use Clone 'clone';
  7         24104  
  7         566  
31              
32 7     7   3693 use Data::Differences;
  7         19  
  7         7282  
33              
34              
35             # require Exporter;
36              
37             our @ISA = qw(Exporter);
38              
39             our @EXPORT_OK = qw(
40             array_comparator
41             hash_comparator
42             data_comparator
43             );
44              
45              
46             #
47             # array_comparator()
48             #
49             # Compare two arrays, report on the differences found by returning an
50             # array describing the differences between the two arrays.
51             #
52              
53             sub array_comparator
54             {
55 63     63 0 82 my $array1 = shift;
56              
57 63         67 my $array2 = shift;
58              
59             # my $result = { adder_operand => [], subtractor_operand => [], };
60              
61 63         89 my $result = [];
62              
63 63         145 foreach my $index (0 .. $#$array1)
64             {
65 133 100       233 if (exists $array2->[$index])
66             {
67 128         259 my $index_result = data_comparator($array1->[$index], $array2->[$index]);
68              
69 128 100       305 if (!$index_result->is_empty())
70             {
71 4         11 $result->[$index] = $index_result;
72             }
73             }
74             else
75             {
76 5         33 $result->[$index]
77             = Data::Differences->new(clone(\$array1->[$index]));
78             }
79             }
80              
81 63         183 foreach my $index ($#$array1 + 1 .. $#$array2)
82             {
83 1         18 $result->[$index]
84             = Data::Differences->new(clone(\$array2->[$index]));
85             }
86              
87 63         198 return Data::Differences->new($result);
88             }
89              
90              
91             =head1 NAME
92              
93             Data::Comparator - recursively compare Perl datatypes
94              
95             =head1 SYNOPSIS
96              
97             use Data::Comparator qw(data_comparator);
98            
99             $a = { 'foo' => 'bar', 'move' => 'zig' };
100             $b = [ 'alpha', 'beta', 'gamma', 'vlissides' ];
101              
102             $diff = data_comparator($a, $b);
103              
104             use Data::Dumper;
105              
106             print Dumper($diff);
107              
108             if ($diff->is_empty())
109             {
110             print '$a and $b are alike\n';
111             }
112             else
113             {
114             print '$a and $b are not alike\n';
115             }
116              
117             =head1 DESCRIPTION
118              
119             Compare two sets of (structured) data, report on the differences found
120             with a differences describing data structure. Additionally a set of
121             expected differences may be given in the form of a differences
122             describing data structure.
123              
124             Returns a differences describing data structure, which is empty if no
125             differences are found. The type of the result is the same as the type
126             of the second data structure given.
127              
128             The algorithm used is of a subtractive kind. It subtracts the first
129             data structure given from the second one. This means that, since it
130             is not possible to subtract what is not given in the subtractor, not
131             all differences are reported. To have a report of all differences
132             between structures A and B, first subtract A from B, next subtract B
133             from A, using this module. The two result sets are an exact
134             description of the differences between A and B.
135              
136             It is possible to add any of the methods array_comparator(),
137             hash_comparator(), data_comparator() to an existing object, or to use
138             these as regular subs.
139              
140             =head1 NOTE
141              
142             This module is used in the tests for Data::Merger(3) and
143             Data::Transformator(3).
144              
145             =head1 BUGS
146              
147             Does only work with scalars, hashes and arrays. Does not work on
148             self-referential structures.
149              
150             =head1 AUTHOR
151              
152             Hugo Cornelis, hugo.cornelis@gmail.com
153              
154             Copyright 2007 Hugo Cornelis.
155              
156             This module is free software; you can redistribute it and/or
157             modify it under the same terms as Perl itself.
158              
159             =head1 SEE ALSO
160              
161             Data::Merger(3), Data::Transformator(3), Data::Differences(3),
162             Clone(3)
163              
164             =cut
165              
166             sub data_comparator
167             {
168 378     378 0 3441 my $data1 = shift;
169              
170 378         367 my $data2 = shift;
171              
172 378         360 my $expected_differences = shift;
173              
174 378         353 my $result;
175              
176             # get the types for the different arguments
177              
178 378   100     2172 my $data_type1 = (ref $data1 && "$data1") || '';
179              
180 378   100     2027 my $data_type2 = (ref $data2 && "$data2") || '';
181              
182             # first compare comparables
183              
184             # try to compare two hashes
185              
186 378 100 100     2727 if ($data_type1 =~ /HASH/
    100 66        
    100 66        
    100 66        
    100 66        
187             && $data_type2 =~ /HASH/)
188             {
189 145         1135 $result = hash_comparator($data1, $data2);
190             }
191              
192             # or try to compare two arrays
193              
194             elsif ($data_type1 =~ /ARRAY/
195             && $data_type2 =~ /ARRAY/)
196             {
197 63         153 $result = array_comparator($data1, $data2);
198             }
199              
200             # or try to compare two scalars
201              
202             elsif ($data_type1 =~ /SCALAR/
203             && $data_type2 =~ /SCALAR/)
204             {
205 6         12 $result = scalar_ref_comparator($data1, $data2);
206             }
207              
208             # or try to compare two referenced references
209              
210             elsif ($data_type1 =~ /REF/
211             && $data_type2 =~ /REF/)
212             {
213 2         4 $result = data_comparator($$data1, $$data2);
214             }
215              
216             # or try to compare two non references
217              
218             elsif (!$data_type1
219             && !$data_type2)
220             {
221 161         663 $result = scalar_comparator($data1, $data2);
222             }
223              
224             # second, for non-comparables
225              
226             else
227             {
228             # simply clone second argument
229              
230 1         10 $result = Data::Differences->new(clone(\$data2));
231             }
232              
233             # if the user was already expecting differences
234              
235 378 100       742 if (defined $expected_differences)
236             {
237             # compare the result with the expected differences
238              
239 6         11 $result = data_comparator($expected_differences, $result);
240             }
241              
242 378         693 return $result;
243             }
244              
245              
246             #
247             # hash_comparator()
248             #
249             # Compare two hashes, report on the differences found by returning an
250             # hash describing the differences between the two hashes.
251             #
252              
253             sub hash_comparator
254             {
255 145     145 0 147 my $hash1 = shift;
256              
257 145         315 my $hash2 = shift;
258              
259 145         178 my $result = {};
260              
261 145         320 foreach my $key (keys %$hash1)
262             {
263 205 100       412 if (exists $hash2->{$key})
264             {
265 182         586 my $key_result = data_comparator($hash1->{$key}, $hash2->{$key});
266              
267 182 100       418 if (!$key_result->is_empty())
268             {
269 3         8 $result->{$key} = $key_result;
270             }
271             }
272             }
273              
274 145         951 foreach my $key (grep { !exists $hash1->{$_} } keys %$hash2)
  184         429  
275             {
276 2         23 $result->{$key}
277             = Data::Differences->new(clone(\$hash2->{$key}));
278             }
279              
280 145         801 return Data::Differences->new($result);
281             }
282              
283              
284             #
285             # scalar_comparator()
286             #
287             # Compare two scalar values, report on the differences found by
288             # returning the second scalar value if it is different from the first
289             # scalar value.
290             #
291              
292             sub scalar_comparator
293             {
294 161     161 0 166 my $scalar1 = shift;
295              
296 161         169 my $scalar2 = shift;
297              
298             #t two undefs is illegal.
299              
300 161 50 66     317 if (!defined $scalar1 && !defined $scalar2)
301             {
302 5         34 return Data::Differences->new(clone(\undef));
303             }
304              
305 156 100       248 if (!defined $scalar2)
306             {
307 5         41 return Data::Differences->new(clone(\$scalar2));
308             }
309              
310 151 100       296 if (($scalar1 cmp $scalar2) eq 0)
311             {
312 148         1020 return Data::Differences->new(clone(\undef));
313             }
314             else
315             {
316 3         33 return Data::Differences->new(clone(\$scalar2));
317             }
318             }
319              
320              
321             #
322             # scalar_ref_comparator()
323             #
324             # Compare two references to scalar values, report on the differences
325             # found by returning the second reference if it is different from the
326             # first reference.
327             #
328              
329             sub scalar_ref_comparator
330             {
331 6     6 0 7 my $scalar1 = shift;
332              
333 6         8 my $scalar2 = shift;
334              
335 6         8 my $value1 = $$scalar1;
336              
337 6         7 my $value2 = $$scalar2;
338              
339              
340             # for two undefs
341              
342 6 50 33     44 if (!defined $value1
    50 33        
343             && !defined $value2)
344             {
345             # return equality
346              
347 0         0 return Data::Differences->new(clone(\undef));
348             }
349              
350             # for one undef
351              
352             elsif (!defined $value1
353             || !defined $value2)
354             {
355             # return different
356              
357 0         0 return Data::Differences->new(clone(\$scalar2));
358             }
359              
360             # in other cases
361              
362             else
363             {
364             # do a normal comparison by calling the generic comparator
365              
366 6         12 return data_comparator($value1, $value2);
367              
368             # if (($value1 cmp $value2) eq 0)
369             # {
370             # return Data::Differences->new(clone(\undef));
371             # }
372             # else
373             # {
374             # return Data::Differences->new(clone(\$scalar2));
375             # }
376             }
377             }
378              
379              
380             1;
381              
382