File Coverage

blib/lib/App/HL7/Compare.pm
Criterion Covered Total %
statement 139 139 100.0
branch 18 20 90.0
condition 9 9 100.0
subroutine 18 18 100.0
pod 2 2 100.0
total 186 188 98.9


line stmt bran cond sub pod time code
1             package App::HL7::Compare;
2             $App::HL7::Compare::VERSION = '0.001';
3 2     2   79865 use v5.10;
  2         15  
4 2     2   11 use strict;
  2         4  
  2         60  
5 2     2   13 use warnings;
  2         4  
  2         53  
6              
7 2     2   1191 use Moo;
  2         23362  
  2         9  
8 2     2   3902 use Mooish::AttributeBuilder -standard;
  2         3656  
  2         13  
9 2     2   1087 use App::HL7::Compare::Parser;
  2         6  
  2         99  
10 2     2   15 use Types::Standard qw(Tuple Str ScalarRef InstanceOf Bool);
  2         6  
  2         16  
11 2     2   6243 use List::Util qw(max);
  2         5  
  2         2890  
12              
13             has param 'files' => (
14             isa => Tuple [Str | ScalarRef, Str | ScalarRef],
15             );
16              
17             has param 'exclude_matching' => (
18             isa => Bool,
19             default => sub { 1 },
20             );
21              
22             has field 'parser' => (
23             isa => InstanceOf ['App::HL7::Compare::Parser'],
24             default => sub { App::HL7::Compare::Parser->new },
25             );
26              
27             sub _compare_line
28             {
29 34     34   65 my ($self, $segment, $field, $component, $subcomponent, $message_num, $comps) = @_;
30              
31 34         133 my $name = sprintf "%s.%s", $segment->name, $segment->number;
32 34   100     104 my $order = $comps->{order}{$name} //= @{$comps->{segments}};
  7         24  
33 34         574 $comps->{segments}[$order]
34             [$field->number][$component->number][$subcomponent->number]
35             [$message_num] = $subcomponent->value;
36             }
37              
38             sub _gather_recursive
39             {
40 82     82   164 my ($self, $item, $levels_down) = @_;
41 82         109 $levels_down -= 1;
42              
43 82         111 my @results;
44 82         111 foreach my $subitem (@{$item->parts}) {
  82         1312  
45 110 100       18004 if ($levels_down == 0) {
46 34         98 push @results, [$subitem];
47             }
48             else {
49             push @results, map {
50 102         154 [$subitem, @{$_}]
  102         272  
51 76         114 } @{$self->_gather_recursive($subitem, $levels_down)};
  76         175  
52             }
53             }
54              
55 82         179 return \@results;
56             }
57              
58             sub _build_comparison_recursive
59             {
60 67     67   102 my ($self, $parts, $levels_down) = @_;
61 67         83 $levels_down -= 1;
62              
63 67 100       113 if ($levels_down == 0) {
64             return [
65             {
66             path => [],
67 23         37 value => [@{$parts}[0, 1]],
  23         86  
68             }
69             ];
70             }
71              
72 44         52 my @results;
73 44         54 foreach my $part_num (0 .. $#{$parts}) {
  44         84  
74 104         145 my $part = $parts->[$part_num];
75 104 100       182 next unless defined $part;
76              
77 60         116 my $deep_results = $self->_build_comparison_recursive($part, $levels_down);
78 60 100 100     71 if (@{$deep_results} == 1 && defined $deep_results->[0]{path}[0]) {
  60         181  
79             $deep_results->[0]{path}[0] = $part_num
80 31 50       64 if $deep_results->[0]{path}[0] == 1;
81 31         59 push @results, $deep_results->[0];
82             }
83             else {
84             push @results, map {
85 38         46 unshift @{$_->{path}}, $part_num;
  38         67  
86 38         86 $_
87 29         66 } @{$deep_results};
  29         59  
88             }
89             }
90              
91 44         98 return \@results;
92             }
93              
94             sub _build_comparison
95             {
96 3     3   7 my ($self, $comps) = @_;
97              
98 3         5 my %reverse_order = map { $comps->{order}{$_} => $_ } keys %{$comps->{order}};
  7         23  
  3         11  
99 3         7 my @results;
100              
101 3         7 foreach my $segment_num (0 .. $#{$comps->{segments}}) {
  3         12  
102 7         11 my $segment = $comps->{segments}[$segment_num];
103             push @results, {
104 7         24 segment => $reverse_order{$segment_num},
105             compared => $self->_build_comparison_recursive($segment, 4)
106             };
107             }
108              
109 3         13 return \@results;
110             }
111              
112             sub _compare_messages
113             {
114 3     3   72 my ($self, $message1, $message2) = @_;
115              
116 3         13 my %comps = (
117             order => {},
118             segments => [],
119             );
120              
121 3         8 my $message_num = 0;
122 3         7 foreach my $message ($message1, $message2) {
123 6         17 my $parts = $self->_gather_recursive($message, 4);
124 6         11 foreach my $part (@{$parts}) {
  6         12  
125 34         656 $self->_compare_line(@{$part}, $message_num, \%comps);
  34         86  
126             }
127              
128 6         147 $message_num += 1;
129             }
130              
131 3         14 return $self->_build_comparison(\%comps);
132             }
133              
134             sub _get_files
135             {
136 3     3   7 my ($self) = @_;
137              
138             my $slurp = sub {
139 4     4   9 my ($file) = @_;
140              
141 4 50       202 open my $fh, '<', $file
142             or die "couldn't open file $file: $!";
143              
144 4         27 local $/;
145 4         206 return readline $fh;
146 3         18 };
147              
148 3         7 my @files = @{$self->files};
  3         17  
149 3         9 foreach my $file (@files) {
150 6 100       19 if (ref $file eq 'SCALAR') {
151 2         4 $file = ${$file};
  2         5  
152             }
153             else {
154 4         9 $file = $slurp->($file);
155             }
156             }
157              
158 3         23 return @files;
159             }
160              
161             sub _remove_matching
162             {
163 3     3   7 my ($self, $compared) = @_;
164              
165 3 100       13 return unless $self->exclude_matching;
166              
167 2         3 foreach my $segment (@{$compared}) {
  2         7  
168 4         6 my @to_delete;
169              
170 4         5 foreach my $comp_num (0 .. $#{$segment->{compared}}) {
  4         12  
171 15         23 my $comp = $segment->{compared}[$comp_num];
172              
173 15         18 my @values = @{$comp->{value}};
  15         32  
174 15 100 100     24 if ((grep { defined } @values) == 2 && $values[0] eq $values[1]) {
  30         77  
175 3         7 push @to_delete, $comp_num;
176             }
177             }
178              
179 4         12 foreach my $comp_num (reverse @to_delete) {
180 3         4 splice @{$segment->{compared}}, $comp_num, 1;
  3         10  
181             }
182             }
183             }
184              
185             sub compare
186             {
187 3     3 1 94 my ($self) = @_;
188              
189 3         11 my $compared = $self->_compare_messages(map { $self->parser->parse($_) } $self->_get_files);
  6         101  
190 3         104 $self->_remove_matching($compared);
191              
192 3         8 return $compared;
193             }
194              
195             sub compare_stringify
196             {
197 2     2 1 116 my ($self) = @_;
198 2         7 my $compared = $self->compare;
199              
200 2         4 my @out;
201 2         4 my $longest = 0;
202 2         5 foreach my $segment (@{$compared}) {
  2         4  
203 6         9 my @stringified;
204 6         9 foreach my $comp (@{$segment->{compared}}) {
  6         10  
205             my $stringified = [
206 23         58 $segment->{segment} . join('', map { "[$_]" } @{$comp->{path}}) . ':',
  13         22  
207 13 100       27 map { defined $_ ? $_ : '(empty)' } @{$comp->{value}}
  26         60  
  13         23  
208             ];
209              
210 13         29 push @stringified, $stringified;
211             }
212              
213 6         10 $longest = max $longest, map { length $_->[0] } @stringified;
  13         31  
214 6         13 my $blueprint = "%-${longest}s %s => %s";
215 6         11 push @out, map { sprintf $blueprint, @{$_} } @stringified;
  13         18  
  13         48  
216             }
217              
218 2         20 return join "\n", @out;
219             }
220              
221             1;
222              
223             # ABSTRACT: compare two HL7 v2 messages against one another
224