ruby-hl7 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2a1652685f5af3d1977d1d32eda59c87bad5523b
4
+ data.tar.gz: 6d995a2efccfb7fd8a0dad2d2c386b530f565824
5
+ SHA512:
6
+ metadata.gz: fbaab918091adf2be1062b6343b9fea752d0210091765b0725b4d5d356fb643578a279e9bf21cb8525d7ba4e58b48073196c23f97138f1c3c2bb43c4d2afe7fe
7
+ data.tar.gz: fd5aaf2b829bc1cdbf3cce507312cba40d33c497915c94ba0accc09b96ca8bb60b2883188e5b7d88f6550f0b7612157811499d418424cfd73ce7242f215943f4
data/README.rdoc CHANGED
@@ -1,5 +1,8 @@
1
1
  = Ruby HL7 Library README
2
2
 
3
+ {<img src="https://travis-ci.org/ruby-hl7/ruby-hl7.png?branch=master" alt="Build Status" />}[https://travis-ci.org/ruby-hl7/ruby-hl7]
4
+ {<img src="https://codeclimate.com/github/ruby-hl7/ruby-hl7.png" />}[https://codeclimate.com/github/ruby-hl7/ruby-hl7]
5
+
3
6
  A simple way to parse and create HL7 2.x messages with Ruby.
4
7
 
5
8
  Examples can be found in HL7::Message.
@@ -7,17 +10,14 @@ Examples can be found in HL7::Message.
7
10
  The version id can be found in the HL7::VERSION constant.
8
11
 
9
12
  * Git: http://github.com/ruby-hl7/ruby-hl7.git
10
- * Docs: http://ruby-hl7.rubyforge.org
13
+ * Docs: http://rubydoc.info/gems/ruby-hl7/1.0.3/frames
11
14
  * Rubygems: https://rubygems.org/gems/ruby-hl7
12
- * Rubyforge: http://rubyforge.org/projects/ruby-hl7
13
15
 
14
16
  Lists
15
17
 
16
- * Developers: mailto:ruby-hl7-devel@rubyforge.org
17
- * Users: mailto:ruby-hl7-users@rubyforge.org
18
18
  * Google group: http://groups.google.com/group/ruby-hl7
19
19
 
20
- Copyright (C) 2006-2010 Mark Guzman
20
+ Copyright (C) 2006-2014 Mark Guzman
21
21
 
22
22
  Maintained by the Collaborative Software Initiative
23
23
 
@@ -0,0 +1,65 @@
1
+ module HL7Time
2
+ # Get a HL7 timestamp (type TS) for a Time instance.
3
+ #
4
+ # fraction_digits:: specifies a number of digits of fractional seconds.
5
+ # Its default value is 0.
6
+ # Time.parse('01:23').to_hl7
7
+ # => "20091202012300"
8
+ # Time.now.to_hl7(3)
9
+ # => "20091202153652.302"
10
+ def to_hl7( fraction_digits = 0)
11
+ strftime('%Y%m%d%H%M%S') + hl7_fractions(fraction_digits)
12
+ end
13
+
14
+ private
15
+ def hl7_fractions(fraction_digits = 0)
16
+ return '' unless fraction_digits > 0
17
+ time_fraction = hl7_time_fraction
18
+ answer = ".#{sprintf('%06d', time_fraction)}"
19
+ answer += '0' * ((fraction_digits - 6)).abs if fraction_digits > 6
20
+ answer[0, 1 + fraction_digits]
21
+ end
22
+
23
+ def hl7_time_fraction
24
+ if respond_to? :usec
25
+ usec
26
+ else
27
+ sec_fraction.to_f * 1000000
28
+ end
29
+ end
30
+ end
31
+
32
+ # Adding the to_hl7 method to the ruby Date class.
33
+ class Date
34
+ # Get a HL7 timestamp (type TS) for a Date instance.
35
+ #
36
+ # Date.parse('2009-12-02').to_hl7
37
+ # => "20091202"
38
+ def to_hl7
39
+ strftime('%Y%m%d')
40
+ end
41
+ end
42
+
43
+ # Adding the to_hl7 method to the ruby Time class.
44
+ class Time
45
+ include HL7Time
46
+ end
47
+
48
+ # Adding the to_hl7 method to the ruby DateTime class.
49
+ class DateTime
50
+ include HL7Time
51
+ end
52
+
53
+ # TODO
54
+ # parse an hl7 formatted date
55
+ #def Date.from_hl7( hl7_date )
56
+ #end
57
+
58
+ #def Date.to_hl7_short( ruby_date )
59
+ #end
60
+
61
+ #def Date.to_hl7_med( ruby_date )
62
+ #end
63
+
64
+ #def Date.to_hl7_long( ruby_date )
65
+ #end
@@ -0,0 +1,6 @@
1
+ # Adding a helper to the String class for the batch parse.
2
+ class String
3
+ def hl7_batch?
4
+ !!match(/^FHS/)
5
+ end
6
+ end
data/lib/message.rb ADDED
@@ -0,0 +1,272 @@
1
+ # Ruby Object representation of an hl7 2.x message
2
+ # the message object is actually a "smart" collection of hl7 segments
3
+ # == Examples
4
+ #
5
+ # ==== Creating a new HL7 message
6
+ #
7
+ # # create a message
8
+ # msg = HL7::Message.new
9
+ #
10
+ # # create a MSH segment for our new message
11
+ # msh = HL7::Message::Segment::MSH.new
12
+ # msh.recv_app = "ruby hl7"
13
+ # msh.recv_facility = "my office"
14
+ # msh.processing_id = rand(10000).to_s
15
+ #
16
+ # msg << msh # add the MSH segment to the message
17
+ #
18
+ # puts msg.to_s # readable version of the message
19
+ #
20
+ # puts msg.to_hl7 # hl7 version of the message (as a string)
21
+ #
22
+ # puts msg.to_mllp # mllp version of the message (as a string)
23
+ #
24
+ # ==== Parse an existing HL7 message
25
+ #
26
+ # raw_input = open( "my_hl7_msg.txt" ).readlines
27
+ # msg = HL7::Message.new( raw_input )
28
+ #
29
+ # puts "message type: %s" % msg[:MSH].message_type
30
+ #
31
+ #
32
+ class HL7::Message
33
+ include Enumerable # we treat an hl7 2.x message as a collection of segments
34
+ extend HL7::MessageBatchParser
35
+
36
+ attr_reader :message_parser
37
+ attr :element_delim
38
+ attr :item_delim
39
+ attr :segment_delim
40
+ attr :delimiter
41
+
42
+ # setup a new hl7 message
43
+ # raw_msg:: is an optional object containing an hl7 message
44
+ # it can either be a string or an Enumerable object
45
+ def initialize( raw_msg=nil, &blk )
46
+ @segments = []
47
+ @segments_by_name = {}
48
+ @item_delim = "^"
49
+ @element_delim = '|'
50
+ @segment_delim = "\r"
51
+ @delimiter = HL7::Message::Delimiter.new( @element_delim,
52
+ @item_delim,
53
+ @segment_delim)
54
+
55
+ @message_parser = HL7::MessageParser.new(@delimiter)
56
+
57
+ parse( raw_msg ) if raw_msg
58
+
59
+ if block_given?
60
+ blk.call self
61
+ end
62
+ end
63
+
64
+ def parse( inobj )
65
+ if inobj.kind_of?(String)
66
+ generate_segments( message_parser.parse_string( inobj ))
67
+ elsif inobj.respond_to?(:each)
68
+ generate_segments_enumerable(inobj)
69
+ else
70
+ raise HL7::ParseError.new( "object to parse should be string or enumerable" )
71
+ end
72
+ end
73
+
74
+ def generate_segments_enumerable(enumerable)
75
+ enumerable.each do |segment|
76
+ generate_segments( message_parser.parse_string( segment.to_s ))
77
+ end
78
+ end
79
+
80
+ # access a segment of the message
81
+ # index:: can be a Range, Fixnum or anything that
82
+ # responds to to_sym
83
+ def []( index )
84
+ ret = nil
85
+
86
+ if index.kind_of?(Range) || index.kind_of?(Fixnum)
87
+ ret = @segments[ index ]
88
+ elsif (index.respond_to? :to_sym)
89
+ ret = @segments_by_name[ index.to_sym ]
90
+ ret = ret.first if ret && ret.length == 1
91
+ end
92
+
93
+ ret
94
+ end
95
+
96
+ # modify a segment of the message
97
+ # index:: can be a Range, Fixnum or anything that
98
+ # responds to to_sym
99
+ # value:: an HL7::Message::Segment object
100
+ def []=( index, value )
101
+ unless ( value && value.kind_of?(HL7::Message::Segment) )
102
+ raise HL7::Exception.new( "attempting to assign something other than an HL7 Segment" )
103
+ end
104
+
105
+ if index.kind_of?(Range) || index.kind_of?(Fixnum)
106
+ @segments[ index ] = value
107
+ elsif index.respond_to?(:to_sym)
108
+ (@segments_by_name[ index.to_sym ] ||= []) << value
109
+ else
110
+ raise HL7::Exception.new( "attempting to use an indice that is not a Range, Fixnum or to_sym providing object" )
111
+ end
112
+
113
+ value.segment_parent = self
114
+ end
115
+
116
+ # return the index of the value if it exists, nil otherwise
117
+ # value:: is expected to be a string
118
+ def index( value )
119
+ return nil unless (value && value.respond_to?(:to_sym))
120
+
121
+ segs = @segments_by_name[ value.to_sym ]
122
+ return nil unless segs
123
+
124
+ @segments.index( segs.to_a.first )
125
+ end
126
+
127
+ # add a segment or array of segments to the message
128
+ # * will force auto set_id sequencing for segments containing set_id's
129
+ def <<( value )
130
+ # do nothing if value is nil
131
+ return unless value
132
+
133
+ if value.kind_of? Array
134
+ value.map{|item| append(item)}
135
+ else
136
+ append(value)
137
+ end
138
+ end
139
+
140
+ def append( value )
141
+ unless ( value && value.kind_of?(HL7::Message::Segment) )
142
+ raise HL7::Exception.new( "attempting to append something other than an HL7 Segment" )
143
+ end
144
+
145
+ value.segment_parent = self unless value.segment_parent
146
+ (@segments ||= []) << value
147
+ name = value.class.to_s.gsub("HL7::Message::Segment::", "").to_sym
148
+ (@segments_by_name[ name ] ||= []) << value
149
+ sequence_segments unless @parsing # let's auto-set the set-id as we go
150
+ end
151
+
152
+ # yield each segment in the message
153
+ def each # :yields: segment
154
+ return unless @segments
155
+ @segments.each { |s| yield s }
156
+ end
157
+
158
+ # return the segment count
159
+ def length
160
+ 0 unless @segments
161
+ @segments.length
162
+ end
163
+
164
+ # provide a screen-readable version of the message
165
+ def to_s
166
+ @segments.collect { |s| s if s.to_s.length > 0 }.join( "\n" )
167
+ end
168
+
169
+ # provide a HL7 spec version of the message
170
+ def to_hl7
171
+ @segments.collect { |s| s if s.to_s.length > 0 }.join( @delimiter.segment )
172
+ end
173
+
174
+ # provide the HL7 spec version of the message wrapped in MLLP
175
+ def to_mllp
176
+ pre_mllp = to_hl7
177
+ "\x0b" + pre_mllp + "\x1c\r"
178
+ end
179
+
180
+ # auto-set the set_id fields of any message segments that
181
+ # provide it and have more than one instance in the message
182
+ def sequence_segments(base=nil)
183
+ last = nil
184
+ segs = @segments
185
+ segs = base.children if base
186
+
187
+ segs.each do |s|
188
+ if s.kind_of?( last.class ) && s.respond_to?( :set_id )
189
+ last.set_id = 1 unless last.set_id && last.set_id.to_i > 0
190
+ s.set_id = last.set_id.to_i + 1
191
+ end
192
+
193
+ sequence_segments( s ) if s.has_children?
194
+
195
+ last = s
196
+ end
197
+ end
198
+
199
+ private
200
+ def generate_segments( ary )
201
+ raise HL7::ParseError.new( "no array to generate segments" ) unless ary.length > 0
202
+
203
+ @parsing = true
204
+ segment_stack = []
205
+ ary.each do |elm|
206
+ if elm.slice(0,3) == "MSH"
207
+ update_delimiters(elm)
208
+ end
209
+ last_seg = generate_segment( elm, segment_stack ) || last_seg
210
+ end
211
+ @parsing = nil
212
+ end
213
+
214
+ def update_delimiters(elm)
215
+ @item_delim = message_parser.parse_item_delim(elm)
216
+ @element_delim = message_parser.parse_element_delim(elm)
217
+ @delimiter.item = @item_delim
218
+ @delimiter.element = @element_delim
219
+ end
220
+
221
+ def generate_segment( elm, segment_stack )
222
+ segment_generator = HL7::Message::SegmentGenerator.new( elm,
223
+ segment_stack,
224
+ @delimiter )
225
+
226
+ return nil unless segment_generator.valid_segments_parts?
227
+ segment_generator.seg_name = segment_generator.seg_parts[0]
228
+
229
+ new_seg = segment_generator.build
230
+ new_seg.segment_parent = self
231
+
232
+ choose_segment_from(segment_stack, new_seg, segment_generator.seg_name)
233
+ end
234
+
235
+ def choose_segment_from(segment_stack, new_seg, seg_name)
236
+
237
+ # Segments have been previously seen
238
+ while(segment_stack.length > 0)
239
+ if segment_stack.last && segment_stack.last.has_children? && segment_stack.last.accepts?( seg_name )
240
+ # If a previous segment can accept the current segment as a child,
241
+ # add it to the previous segments children
242
+ segment_stack.last.children << new_seg
243
+ new_seg.is_child_segment = true
244
+ segment_stack << new_seg
245
+ break;
246
+ else
247
+ segment_stack.pop
248
+ end
249
+ end
250
+
251
+ # Root of segment 'tree'
252
+ if segment_stack.length == 0
253
+ @segments << new_seg
254
+ segment_stack << new_seg
255
+ setup_segment_lookup_by_name( seg_name, new_seg)
256
+ end
257
+ end
258
+
259
+ # Allow segment lookup by name
260
+ def setup_segment_lookup_by_name(seg_name, new_seg)
261
+ seg_sym = get_symbol_from_name(seg_name)
262
+ if seg_sym
263
+ @segments_by_name[ seg_sym ] ||= []
264
+ @segments_by_name[ seg_sym ] << new_seg
265
+ end
266
+ end
267
+
268
+ def get_symbol_from_name(seg_name)
269
+ seg_name.to_s.strip.length > 0 ? seg_name.to_sym : nil
270
+ end
271
+
272
+ end
@@ -0,0 +1,75 @@
1
+ module HL7::MessageBatchParser
2
+ def parse_batch(batch) # :yields: message
3
+ raise HL7::ParseError, 'badly_formed_batch_message' unless
4
+ batch.hl7_batch?
5
+
6
+ batch = clean_batch_for_jruby batch
7
+
8
+ raise HL7::ParseError, 'empty_batch_message' unless
9
+ match = /\rMSH/.match(batch)
10
+
11
+ match.post_match.split(/\rMSH/).each do |_msg|
12
+ if md = /\rBTS/.match(_msg)
13
+ # TODO: Validate the message count in the BTS segment
14
+ # should == index + 1
15
+ _msg = md.pre_match
16
+ end
17
+
18
+ yield 'MSH' + _msg
19
+ end
20
+ end
21
+
22
+ # parse a String or Enumerable object into an HL7::Message if possible
23
+ # * returns a new HL7::Message if successful
24
+ def parse( inobj )
25
+ HL7::Message.new do |msg|
26
+ msg.parse( inobj )
27
+ end
28
+ end
29
+
30
+ # JRuby seems to change our literal \r characters in sample
31
+ # messages (from here documents) into newlines. We make a copy
32
+ # here, reverting to carriage returns. The input is unchanged.
33
+ #
34
+ # This does not occur when posts are received with CR
35
+ # characters, only in sample messages from here documents. The
36
+ # expensive copy is only incurred when the batch message has a
37
+ # newline character in it.
38
+ private
39
+
40
+ def clean_batch_for_jruby(batch)
41
+ batch.gsub("\n", "\r") if batch.include?("\n")
42
+ end
43
+ end
44
+
45
+ # Provides basic methods to parse_string, element and item delimeter parser
46
+ class HL7::MessageParser
47
+ attr_reader :delimiter
48
+
49
+ def self.split_by_delimiter(element, delimiter)
50
+ element.split( delimiter, -1 )
51
+ end
52
+
53
+ def initialize(delimiter)
54
+ @delimiter = delimiter
55
+ end
56
+
57
+ # parse the provided String or Enumerable object into this message
58
+ def parse_string( instr )
59
+ post_mllp = instr
60
+ if /\x0b((:?.|\r|\n)+)\x1c\r/.match( instr )
61
+ post_mllp = $1 #strip the mllp bytes
62
+ end
63
+ HL7::MessageParser.split_by_delimiter(post_mllp, @delimiter.segment)
64
+ end
65
+
66
+ # Get the element delimiter from an MSH segment
67
+ def parse_element_delim(str)
68
+ (str && str.kind_of?(String)) ? str.slice(3,1) : "|"
69
+ end
70
+
71
+ # Get the item delimiter from an MSH segment
72
+ def parse_item_delim(str)
73
+ (str && str.kind_of?(String)) ? str.slice(4,1) : "^"
74
+ end
75
+ end