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 +7 -0
- data/README.rdoc +5 -5
- data/lib/core_ext/date_time.rb +65 -0
- data/lib/core_ext/string.rb +6 -0
- data/lib/message.rb +272 -0
- data/lib/message_parser.rb +75 -0
- data/lib/ruby-hl7.rb +15 -637
- data/lib/segment.rb +189 -0
- data/lib/segment_default.rb +18 -0
- data/lib/segment_fields.rb +117 -0
- data/lib/segment_generator.rb +55 -0
- data/lib/segment_list_storage.rb +79 -0
- data/lib/segments/evn.rb +14 -8
- data/lib/segments/in1.rb +54 -0
- data/lib/segments/msh.rb +23 -17
- data/lib/segments/obr.rb +23 -10
- data/lib/segments/obx.rb +15 -4
- data/lib/segments/orc.rb +19 -3
- data/lib/segments/pid.rb +13 -6
- data/lib/segments/prd.rb +18 -0
- data/lib/segments/pv1.rb +6 -2
- data/lib/segments/pv2.rb +18 -15
- data/lib/segments/rf1.rb +22 -0
- data/lib/segments/zcf.rb +23 -0
- data/test_data/obxobr.hl7 +1 -1
- metadata +94 -110
- data/lib/string.rb +0 -5
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.
|
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-
|
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
|
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
|