File Coverage

lib/Net/HL7/Message.pm
Criterion Covered Total %
statement 151 157 96.1
branch 47 54 87.0
condition 8 9 88.8
subroutine 19 20 95.0
pod 12 12 100.0
total 237 252 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 7     7   4571 use 5.004;
  7         18  
  7         246  
14 7     7   35 use strict;
  7         10  
  7         182  
15 7     7   33 use warnings;
  7         7  
  7         171  
16 7     7   1602 use Net::HL7::Segment;
  7         10  
  7         188  
17 7     7   1894 use Net::HL7;
  7         10  
  7         7519  
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 23     23 1 3749 my $class = shift;
96 23         68 bless my $self = {}, $class;
97            
98 23 100       106 $self->_init(@_) || return undef;
99            
100 21         56 return $self;
101             }
102              
103              
104             sub _init {
105              
106 23     23   45 my ($self, $hl7str) = @_;
107              
108             # Array holding the segments
109             #
110 23         82 $self->{SEGMENTS} = [];
111              
112             # Control characters and other HL7 properties
113             #
114 23         36 $self->{SEGMENT_SEPARATOR} = $Net::HL7::SEGMENT_SEPARATOR;
115 23         33 $self->{FIELD_SEPARATOR} = $Net::HL7::FIELD_SEPARATOR;
116 23         31 $self->{COMPONENT_SEPARATOR} = $Net::HL7::COMPONENT_SEPARATOR;
117 23         35 $self->{SUBCOMPONENT_SEPARATOR} = $Net::HL7::SUBCOMPONENT_SEPARATOR;
118 23         26 $self->{REPETITION_SEPARATOR} = $Net::HL7::REPETITION_SEPARATOR;
119 23         33 $self->{ESCAPE_CHARACTER} = $Net::HL7::ESCAPE_CHARACTER;
120 23         43 $self->{HL7_VERSION} = $Net::HL7::HL7_VERSION;
121              
122             # If an HL7 string is given to the constructor, parse it.
123 23 100       71 if ($hl7str) {
124              
125 11         91 my @segments = split("[\n\\" . $self->{SEGMENT_SEPARATOR} . "]", $hl7str);
126              
127             # the first segment should be the control segment
128             #
129 11         37 $segments[0] =~ /^([A-Z0-9]{3})(.)(.)(.)(.)(.)(.)/;
130              
131 11         51 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 11 100       26 if ($fldSep ne $fldSepCtrl) {
137              
138 1         6 return undef;
139             }
140              
141             # Set field separator based on control segment
142 10         20 $self->{FIELD_SEPARATOR} = $fldSep;
143            
144             # Set other separators
145 10         13 $self->{COMPONENT_SEPARATOR} = $compSep;
146 10         13 $self->{SUBCOMPONENT_SEPARATOR} = $subCompSep;
147 10         14 $self->{ESCAPE_CHARACTER} = $esc;
148 10         9 $self->{REPETITION_SEPARATOR} = $repSep;
149            
150             # Do all segments
151             #
152 10         21 for (my $i = 0; $i < @segments; $i++) {
153            
154 19         143 my @fields = split('\\' . $self->{FIELD_SEPARATOR}, $segments[$i]);
155              
156 19         32 my $name = shift(@fields);
157              
158             # Now decompose fields if necessary, into refs to arrays
159             #
160 19         36 for (my $j = 0; $j < @fields; $j++) {
161              
162             # Skip control field
163 82 100 100     199 if ($i == 0 && $j == 0) {
164            
165 10         19 next;
166             }
167            
168 72         148 my @comps = split('\\' . $self->{COMPONENT_SEPARATOR}, $fields[$j]);
169            
170 72         115 for (my $k = 0; $k < @comps; $k++) {
171              
172 50         110 my @subComps = split('\\' . $self->{SUBCOMPONENT_SEPARATOR}, $comps[$k]);
173            
174             # Make it a ref or just the value
175 50 100       71 if (@subComps <= 1) {
176 47         94 $comps[$k] = $subComps[0];
177             }
178             else {
179 3         10 $comps[$k] = \@subComps;
180             }
181              
182             }
183              
184 72 100       77 if (@comps <= 1) {
185 66         112 $fields[$j] = $comps[0];
186             }
187             else {
188 6         13 $fields[$j] = \@comps;
189             }
190             }
191              
192 19         20 my $seg;
193              
194             # untaint
195 19         26 my $segClass = "";
196              
197 19 100       63 if ($name =~ /^[A-Z][A-Z0-9]{2}$/) {
198 18         19 $segClass = "Net::HL7::Segments::$name";
199 18         38 $segClass =~ /^(.*)$/;
200 18         31 $segClass = $1;
201             }
202              
203             # Let's see whether it's a special segment
204             #
205 19 100 100     906 if ( $segClass && eval("require $segClass;") ) {
206 10         22 unshift(@fields, $self->{FIELD_SEPARATOR});
207 10         11 $seg = eval{ "$segClass"->new(\@fields); };
  10         41  
208             }
209             else {
210 9         35 $seg = new Net::HL7::Segment($name, \@fields);
211             }
212            
213 19 100       69 $seg || return undef;
214              
215 18         40 $self->addSegment($seg);
216             }
217             }
218              
219 21         44 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 42     42 1 51 my ($self, $segment) = @_;
235              
236 42 100       33 if (@{ $self->{SEGMENTS} } == 0) {
  42         84  
237 22         56 $self->_resetCtrl($segment);
238             }
239              
240 42         41 push( @{ $self->{SEGMENTS} }, $segment);
  42         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 10 my ($self, $segment, $idx) = @_;
257              
258 4 50       8 (! defined $idx) && return;
259 4 100       2 ($idx > @{ $self->{SEGMENTS} }) && return;
  4         9  
260              
261 3 50       4 if ($idx == 0) {
  3 100       8  
262              
263 0         0 $self->_resetCtrl($segment);
264 0         0 unshift(@{ $self->{SEGMENTS} }, $segment);
  0         0  
265             }
266             elsif ($idx == @{ $self->{SEGMENTS} }) {
267              
268 1         1 push(@{ $self->{SEGMENTS} }, $segment);
  1         2  
269             }
270             else {
271 2         5 @{ $self->{SEGMENTS} } =
  2         4  
272 2         1 (@{ $self->{SEGMENTS} }[0..$idx-1],
273             $segment,
274 2         2 @{ $self->{SEGMENTS} }[$idx..@{ $self->{SEGMENTS} } -1]
  2         4  
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 150     150 1 507 my ($self, $index) = @_;
292              
293 150         280 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 4 my ($self, $name) = @_;
308              
309 2         5 my @segments = ();
310              
311 2         2 foreach (@{ $self->{SEGMENTS} }) {
  2         5  
312 7 100       9 ($_->getName() eq $name) && push(@segments, $_);
313             }
314              
315 2         6 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 6 my ($self, $index) = @_;
332              
333 4 100       4 ($index < @{ $self->{SEGMENTS} }) && splice( @{ $self->{SEGMENTS} }, $index, 1);
  3         5  
  4         9  
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         12  
354              
355 2 100 66     5 if ($segment->getName() eq "MSH" && $idx == 0) {
356              
357 1         11 $self->_resetCtrl($segment);
358             }
359            
360 2         3 @{ $self->{SEGMENTS} }[$idx] = $segment;
  2         5  
361             }
362              
363              
364             # After change of MSH, reset control fields
365             #
366             sub _resetCtrl {
367              
368 33     33   31 my ($self, $segment) = @_;
369              
370 33 50       65 if ($segment->getField(1)) {
371 33         110 $self->{FIELD_SEPARATOR} = $segment->getField(1);
372             }
373            
374 33 50       64 if ($segment->getField(2) =~ /(.)(.)(.)(.)/) {
375            
376 33         77 $self->{COMPONENT_SEPARATOR} = $1;
377 33         65 $self->{REPETITION_SEPARATOR} = $2;
378 33         61 $self->{ESCAPE_CHARACTER} = $3;
379 33         62 $self->{SUBCOMPONENT_SEPARATOR} = $4;
380             }
381            
382 33 100       61 if ($segment->getField(12)) {
383 17         34 $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 10     10 1 32 my ($self, $pretty) = @_;
418 10         17 my $msg = "";
419              
420             # Make sure MSH(1) and MSH(2) are ok, even if someone has changed
421             # these values
422             #
423 10         22 my $msh = $self->{SEGMENTS}->[0];
424              
425 10         21 $self->_resetCtrl($msh);
426              
427 10         16 for (my $i = 0; $i < @{ $self->{SEGMENTS} }; $i++) {
  26         51  
428            
429 16         46 $msg .= $self->getSegmentAsString($i);
430              
431 16 100       31 $pretty ? ($msg .= "\n") : ($msg .= $self->{SEGMENT_SEPARATOR});
432             }
433            
434 10         111 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 20     20 1 22 my ($self, $index) = @_;
451              
452 20         45 my $seg = $self->getSegmentByIndex($index);
453              
454 20 50       33 $seg || return undef;
455              
456 20         61 my $segStr = $seg->getName() . $self->{FIELD_SEPARATOR};
457            
458 20 100       31 my $start = $seg->getName() eq "MSH" ? 2 : 1;
459              
460             {
461 7     7   40 no warnings;
  7         12  
  7         1343  
  20         36  
462            
463 20         51 foreach ($start..$seg->size()) {
464            
465 79         103 $segStr .= $self->getSegmentFieldAsString($index, $_);
466 79         98 $segStr .= $self->{FIELD_SEPARATOR};
467             }
468             }
469            
470 20         39 return $segStr;
471             }
472              
473              
474             =pod
475              
476             =item B
477              
478              
479             =cut
480              
481             sub getSegmentFieldAsString {
482            
483 81     81 1 71 my ($self, $segIndex, $fldIndex) = @_;
484              
485 81         85 my $seg = $self->getSegmentByIndex($segIndex);
486              
487 81 50       113 $seg || return undef;
488              
489 81         118 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         1 foreach (@{ $self->{SEGMENTS} }) {
  1         3  
511 2 100       5 if ($_->getName() eq $name) {
512 1         1 splice( @{ $self->{SEGMENTS} }, $i, 1);
  1         6  
513             }
514             else {
515 1         7 $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