File Coverage

lib/Net/HL7/Message.pm
Criterion Covered Total %
statement 150 156 96.1
branch 47 54 87.0
condition 8 9 88.8
subroutine 19 20 95.0
pod 12 12 100.0
total 236 251 94.0


line stmt bran cond sub pod time code
1             ################################################################################
2             #
3             # File : Message.pm
4             # Author : Duco Dokter
5             # Created : Mon Nov 11 17:37:11 2002
6             # Version : $Id: Message.pm,v 1.21 2015/01/29 15:30:11 wyldebeast Exp $
7             # Copyright : D.A.Dokter, Wyldebeast & Wunderliebe
8             #
9             ################################################################################
10              
11             package Net::HL7::Message;
12              
13 4     4   1376 use 5.004;
  4         11  
14 4     4   18 use strict;
  4         5  
  4         76  
15 4     4   24 use warnings;
  4         7  
  4         123  
16 4     4   843 use Net::HL7::Segment;
  4         7  
  4         98  
17 4     4   1323 use Net::HL7;
  4         8  
  4         5531  
18              
19             =pod
20              
21             =head1 NAME
22              
23             Net::HL7::Message
24              
25             =head1 SYNOPSIS
26              
27             my $request = new Net::HL7::Request();
28             my $conn = new Net::HL7::Connection('localhost', 8089);
29              
30             my $msh = new Net::HL7::Segments::MSH();
31              
32             my $seg1 = new Net::HL7::Segment("PID");
33              
34             $seg1->setField(1, "foo");
35              
36             $request->addSegment($msh);
37             $request->addSegment($seg1);
38              
39             my $response = $conn->send($request);
40              
41              
42             =head1 DESCRIPTION
43              
44             In general one needn't create an instance of the Net::HL7::Message
45             class directly, but use the L
46             class. When adding segments, note that the segment index starts at 0,
47             so to get the first segment, segment, do
48             C<$msg-EgetSegmentByIndex(0)>.
49              
50             The segment separator defaults to \015. To change this, set the
51             variable $Net::HL7::SEGMENT_SEPARATOR.
52              
53              
54             =head1 METHODS
55              
56             =over 4
57              
58             =item B<$msg = new Net::HL7::Message([$msg])>
59              
60             The constructor takes an optional string argument that is a string
61             representation of a HL7 message. If the string representation is not a
62             valid HL7 message. according to the specifications, undef is returned
63             instead of a new instance. This means that segments should be
64             separated within the message with the segment separator (defaults to
65             \015) or a newline, and segments should be syntactically correct.
66             When using the string argument constructor, make sure that you have
67             escaped any characters that would have special meaning in Perl. For
68             instance (using a different subcomponent separator):
69              
70             C<$msg = new Net::HL7::Message("MSH*^~\\@*1\rPID***x^x@y@z^z\r");>
71              
72             would actually mean
73              
74             C<$msg = new Net::HL7::Message("MSH*^~\\@*1\rPID***x^x^z\r");>
75              
76             since '@y@z' would be interpreted as two empty arrays, so do:
77              
78             C<$msg = new Net::HL7::Message("MSH*^~\\@*1\rPID***x^x\@y\@z^z\r");>
79              
80             instead.
81              
82             The control characters and field separator will take the values from
83             the MSH segment, if set. Otherwise defaults will be used. Changing the
84             MSH fields specifying the field separator and control characters after
85             the MSH has been added to the message will result in setting these
86             values for the message.
87              
88             If the message couldn't be created, for example due to a erroneous HL7
89             message string, undef is returned.
90              
91             =cut
92              
93             sub new {
94            
95 18     18 1 2452 my $class = shift;
96 18         37 bless my $self = {}, $class;
97            
98 18 100       66 $self->_init(@_) || return undef;
99            
100 16         38 return $self;
101             }
102              
103              
104             sub _init {
105              
106 18     18   48 my ($self, $hl7str) = @_;
107              
108             # Array holding the segments
109             #
110 18         50 $self->{SEGMENTS} = [];
111              
112             # Control characters and other HL7 properties
113             #
114 18         28 $self->{SEGMENT_SEPARATOR} = $Net::HL7::SEGMENT_SEPARATOR;
115 18         27 $self->{FIELD_SEPARATOR} = $Net::HL7::FIELD_SEPARATOR;
116 18         31 $self->{COMPONENT_SEPARATOR} = $Net::HL7::COMPONENT_SEPARATOR;
117 18         22 $self->{SUBCOMPONENT_SEPARATOR} = $Net::HL7::SUBCOMPONENT_SEPARATOR;
118 18         25 $self->{REPETITION_SEPARATOR} = $Net::HL7::REPETITION_SEPARATOR;
119 18         29 $self->{ESCAPE_CHARACTER} = $Net::HL7::ESCAPE_CHARACTER;
120 18         39 $self->{HL7_VERSION} = $Net::HL7::HL7_VERSION;
121              
122             # If an HL7 string is given to the constructor, parse it.
123 18 100       40 if ($hl7str) {
124              
125 9         128 my @segments = split("[\n\\" . $self->{SEGMENT_SEPARATOR} . "]", $hl7str);
126              
127             # the first segment should be the control segment
128             #
129 9         28 $segments[0] =~ /^([A-Z0-9]{3})(.)(.)(.)(.)(.)(.)/;
130              
131 9         37 my ($hdr, $fldSep, $compSep, $repSep, $esc, $subCompSep, $fldSepCtrl) =
132             ($1, $2, $3, $4, $5, $6, $7);
133              
134             # Check whether field separator is repeated after 4 control characters
135              
136 9 100       20 if ($fldSep ne $fldSepCtrl) {
137              
138 1         9 return undef;
139             }
140              
141             # Set field separator based on control segment
142 8         14 $self->{FIELD_SEPARATOR} = $fldSep;
143            
144             # Set other separators
145 8         13 $self->{COMPONENT_SEPARATOR} = $compSep;
146 8         12 $self->{SUBCOMPONENT_SEPARATOR} = $subCompSep;
147 8         12 $self->{ESCAPE_CHARACTER} = $esc;
148 8         11 $self->{REPETITION_SEPARATOR} = $repSep;
149            
150             # Do all segments
151             #
152 8         23 for (my $i = 0; $i < @segments; $i++) {
153            
154 15         144 my @fields = split('\\' . $self->{FIELD_SEPARATOR}, $segments[$i]);
155              
156 15         25 my $name = shift(@fields);
157              
158             # Now decompose fields if necessary, into refs to arrays
159             #
160 15         35 for (my $j = 0; $j < @fields; $j++) {
161              
162             # Skip control field
163 55 100 100     163 if ($i == 0 && $j == 0) {
164            
165 8         19 next;
166             }
167            
168 47         287 my @comps = split('\\' . $self->{COMPONENT_SEPARATOR}, $fields[$j]);
169            
170 47         112 for (my $k = 0; $k < @comps; $k++) {
171              
172 40         225 my @subComps = split('\\' . $self->{SUBCOMPONENT_SEPARATOR}, $comps[$k]);
173            
174             # Make it a ref or just the value
175 40 100       79 if (@subComps <= 1) {
176 37         106 $comps[$k] = $subComps[0];
177             }
178             else {
179 3         10 $comps[$k] = \@subComps;
180             }
181              
182             }
183              
184 47 100       75 if (@comps <= 1) {
185 41         104 $fields[$j] = $comps[0];
186             }
187             else {
188 6         17 $fields[$j] = \@comps;
189             }
190             }
191              
192 15         17 my $seg;
193              
194             # untaint
195 15         17 my $segClass = "";
196              
197 15 100       43 if ($name =~ /^[A-Z][A-Z0-9]{2}$/) {
198 14         18 $segClass = "Net::HL7::Segments::$name";
199 14         33 $segClass =~ /^(.*)$/;
200 14         26 $segClass = $1;
201             }
202              
203             # Let's see whether it's a special segment
204             #
205 15 100 100     603 if ( $segClass && eval("require $segClass;") ) {
206 8         19 unshift(@fields, $self->{FIELD_SEPARATOR});
207 8         10 $seg = eval{ "$segClass"->new(\@fields); };
  8         28  
208             }
209             else {
210 7         29 $seg = new Net::HL7::Segment($name, \@fields);
211             }
212            
213 15 100       63 $seg || return undef;
214              
215 14         28 $self->addSegment($seg);
216             }
217             }
218              
219 16         38 return 1;
220             }
221              
222              
223             =pod
224              
225             =item B
226              
227             Add the segment. to the end of the message. The segment should be an
228             instance of L.
229              
230             =cut
231              
232             sub addSegment {
233              
234 32     32 1 46 my ($self, $segment) = @_;
235              
236 32 100       32 if (@{ $self->{SEGMENTS} } == 0) {
  32         87  
237 17         34 $self->_resetCtrl($segment);
238             }
239              
240 32         40 push( @{ $self->{SEGMENTS} }, $segment);
  32         113  
241             }
242              
243              
244             =pod
245              
246             =item B
247              
248             Insert the segment. The segment should be an instance of
249             L. If the index is not given,
250             nothing happens.
251              
252             =cut
253              
254             sub insertSegment {
255              
256 4     4 1 11 my ($self, $segment, $idx) = @_;
257              
258 4 50       10 (! defined $idx) && return;
259 4 100       5 ($idx > @{ $self->{SEGMENTS} }) && return;
  4         12  
260              
261 3 50       7 if ($idx == 0) {
    100          
262              
263 0         0 $self->_resetCtrl($segment);
264 0         0 unshift(@{ $self->{SEGMENTS} }, $segment);
  0         0  
265             }
266 3         7 elsif ($idx == @{ $self->{SEGMENTS} }) {
267              
268 1         2 push(@{ $self->{SEGMENTS} }, $segment);
  1         3  
269             }
270             else {
271 2         6 @{ $self->{SEGMENTS} } =
272 2         6 (@{ $self->{SEGMENTS} }[0..$idx-1],
273             $segment,
274 2         5 @{ $self->{SEGMENTS} }[$idx..@{ $self->{SEGMENTS} } -1]
  2         4  
  2         3  
275             );
276             }
277             }
278              
279              
280             =pod
281              
282             =item B
283              
284             Return the segment specified by $index. Segment count within the
285             message starts at 0.
286              
287             =cut
288              
289             sub getSegmentByIndex {
290              
291 115     115 1 203 my ($self, $index) = @_;
292              
293 115         237 return $self->{SEGMENTS}->[$index];
294             }
295              
296              
297             =pod
298              
299             =item B
300              
301             Return an array of all segments with the given name
302              
303             =cut
304              
305             sub getSegmentsByName {
306              
307 2     2 1 5 my ($self, $name) = @_;
308              
309 2         3 my @segments = ();
310              
311 2         4 foreach (@{ $self->{SEGMENTS} }) {
  2         11  
312 7 100       15 ($_->getName() eq $name) && push(@segments, $_);
313             }
314              
315 2         7 return @segments;
316             }
317              
318              
319             =pod
320              
321             =item B
322              
323             Remove the segment indexed by $index. If it doesn't exist, nothing
324             happens, if it does, all segments after this one will be moved one
325             index up.
326              
327             =cut
328              
329             sub removeSegmentByIndex {
330              
331 4     4 1 8 my ($self, $index) = @_;
332              
333 4 100       5 ($index < @{ $self->{SEGMENTS} }) && splice( @{ $self->{SEGMENTS} }, $index, 1);
  3         8  
  4         12  
334             }
335              
336              
337             =pod
338              
339             =item B
340              
341             Set the segment on index. If index is out of range, or not provided,
342             do nothing. Setting MSH on index 0 will revalidate field separator,
343             control characters and hl7 version, based on MSH(1), MSH(2) and
344             MSH(12).
345              
346             =cut
347              
348             sub setSegment {
349              
350 3     3 1 7 my ($self, $segment, $idx) = @_;
351              
352 3 100       6 (! defined $idx) && return;
353 2 50       3 ($idx > @{ $self->{SEGMENTS} }) && return;
  2         7  
354              
355 2 100 66     5 if ($segment->getName() eq "MSH" && $idx == 0) {
356              
357 1         9 $self->_resetCtrl($segment);
358             }
359            
360 2         3 @{ $self->{SEGMENTS} }[$idx] = $segment;
  2         6  
361             }
362              
363              
364             # After change of MSH, reset control fields
365             #
366             sub _resetCtrl {
367              
368 26     26   31 my ($self, $segment) = @_;
369              
370 26 50       54 if ($segment->getField(1)) {
371 26         56 $self->{FIELD_SEPARATOR} = $segment->getField(1);
372             }
373            
374 26 50       63 if ($segment->getField(2) =~ /(.)(.)(.)(.)/) {
375            
376 26         56 $self->{COMPONENT_SEPARATOR} = $1;
377 26         43 $self->{REPETITION_SEPARATOR} = $2;
378 26         44 $self->{ESCAPE_CHARACTER} = $3;
379 26         43 $self->{SUBCOMPONENT_SEPARATOR} = $4;
380             }
381            
382 26 100       60 if ($segment->getField(12)) {
383 10         22 $self->{HL7_VERSION} = $segment->getField(12);
384             }
385             }
386              
387              
388             =pod
389              
390             =item B
391              
392             Return an array containing all segments in the right order.
393              
394             =cut
395              
396             sub getSegments {
397              
398 0     0 1 0 my $self = shift;
399              
400 0         0 return @{ $self->{SEGMENTS} };
  0         0  
401             }
402              
403              
404             =pod
405              
406             =item B
407              
408             Return a string representation of this message. This can be used to
409             send the message over a socket to an HL7 server. To print to other
410             output, use the $pretty argument as some true value. This will not use
411             the default segment separator, but '\n' instead.
412              
413             =cut
414              
415             sub toString {
416            
417 8     8 1 33 my ($self, $pretty) = @_;
418 8         11 my $msg = "";
419              
420             # Make sure MSH(1) and MSH(2) are ok, even if someone has changed
421             # these values
422             #
423 8         14 my $msh = $self->{SEGMENTS}->[0];
424              
425 8         12 $self->_resetCtrl($msh);
426              
427 8         10 for (my $i = 0; $i < @{ $self->{SEGMENTS} }; $i++) {
  20         46  
428            
429 12         23 $msg .= $self->getSegmentAsString($i);
430              
431 12 100       31 $pretty ? ($msg .= "\n") : ($msg .= $self->{SEGMENT_SEPARATOR});
432             }
433            
434 8         31 return $msg;
435             }
436              
437              
438             =pod
439              
440             =item B
441              
442             Get the string representation of the segment, in the context of this
443             message. That means the string representation will use the message's
444             separators.
445              
446             =cut
447              
448             sub getSegmentAsString {
449              
450 16     16 1 24 my ($self, $index) = @_;
451              
452 16         27 my $seg = $self->getSegmentByIndex($index);
453              
454 16 50       30 $seg || return undef;
455              
456 16         36 my $segStr = $seg->getName() . $self->{FIELD_SEPARATOR};
457            
458 16 100       34 my $start = $seg->getName() eq "MSH" ? 2 : 1;
459              
460             {
461 4     4   23 no warnings;
  4         4  
  4         902  
  16         42  
462            
463 16         36 foreach ($start..$seg->size()) {
464            
465 52         100 $segStr .= $self->getSegmentFieldAsString($index, $_);
466 52         90 $segStr .= $self->{FIELD_SEPARATOR};
467             }
468             }
469            
470 16         39 return $segStr;
471             }
472              
473              
474             =pod
475              
476             =item B
477              
478              
479             =cut
480              
481             sub getSegmentFieldAsString {
482            
483 54     54 1 61 my ($self, $segIndex, $fldIndex) = @_;
484              
485 54         81 my $seg = $self->getSegmentByIndex($segIndex);
486              
487 54 50       100 $seg || return undef;
488              
489 54         98 return $seg->getFieldAsString($fldIndex);
490             }
491              
492              
493             =pod
494              
495             =item B
496              
497             Remove the segment indexed by $name. If it doesn't exist, nothing
498             happens, if it does, all segments after this one will be moved one
499             index up.
500              
501             =back
502              
503             =cut
504              
505             sub removeSegmentByName {
506              
507 1     1 1 2 my ($self, $name) = @_;
508 1         2 my $i = 0;
509              
510 1         3 foreach (@{ $self->{SEGMENTS} }) {
  1         3  
511 2 100       5 if ($_->getName() eq $name) {
512 1         2 splice( @{ $self->{SEGMENTS} }, $i, 1);
  1         6  
513             }
514             else {
515 1         3 $i++;
516             }
517             }
518             }
519              
520              
521             1;
522              
523             =pod
524              
525             =head1 AUTHOR
526              
527             D.A.Dokter
528              
529             =head1 LICENSE
530              
531             Copyright (c) 2002 D.A.Dokter. All rights reserved. This program is
532             free software; you can redistribute it and/or modify it under the same
533             terms as Perl itself.
534              
535             =cut