lib/message.rb
# Ruby Object representation of an hl7 2.x message
# the message object is actually a "smart" collection of hl7 segments
# == Examples
#
# ==== Creating a new HL7 message
#
# # create a message
# msg = HL7::Message.new
#
# # create a MSH segment for our new message
# msh = HL7::Message::Segment::MSH.new
# msh.recv_app = "ruby hl7"
# msh.recv_facility = "my office"
# msh.processing_id = rand(10000).to_s
#
# msg << msh # add the MSH segment to the message
#
# puts msg.to_s # readable version of the message
#
# puts msg.to_hl7 # hl7 version of the message (as a string)
#
# puts msg.to_mllp # mllp version of the message (as a string)
#
# ==== Parse an existing HL7 message
#
# raw_input = open( "my_hl7_msg.txt" ).readlines
# msg = HL7::Message.new( raw_input )
#
# puts "message type: %s" % msg[:MSH].message_type
#
#
class HL7::Message
include Enumerable # we treat an hl7 2.x message as a collection of segments
extend HL7::MessageBatchParser
attr_reader :message_parser
attr :element_delim
attr :item_delim
attr :segment_delim
attr :delimiter
# setup a new hl7 message
# raw_msg:: is an optional object containing an hl7 message
# it can either be a string or an Enumerable object
def initialize( raw_msg=nil, &blk )
@segments = []
@segments_by_name = {}
@item_delim = "^"
@element_delim = '|'
@segment_delim = "\r"
@delimiter = HL7::Message::Delimiter.new( @element_delim,
@item_delim,
@segment_delim)
@message_parser = HL7::MessageParser.new(@delimiter)
parse( raw_msg ) if raw_msg
if block_given?
blk.call self
end
end
def parse( inobj )
if inobj.kind_of?(String)
generate_segments( message_parser.parse_string( inobj ))
elsif inobj.respond_to?(:each)
generate_segments_enumerable(inobj)
else
raise HL7::ParseError.new( "object to parse should be string or enumerable" )
end
end
def generate_segments_enumerable(enumerable)
enumerable.each do |segment|
generate_segments( message_parser.parse_string( segment.to_s ))
end
end
# access a segment of the message
# index:: can be a Range, Fixnum or anything that
# responds to to_sym
def []( index )
ret = nil
if index.kind_of?(Range) || index.kind_of?(Fixnum)
ret = @segments[ index ]
elsif (index.respond_to? :to_sym)
ret = @segments_by_name[ index.to_sym ]
ret = ret.first if ret && ret.length == 1
end
ret
end
# modify a segment of the message
# index:: can be a Range, Fixnum or anything that
# responds to to_sym
# value:: an HL7::Message::Segment object
def []=( index, value )
unless ( value && value.kind_of?(HL7::Message::Segment) )
raise HL7::Exception.new( "attempting to assign something other than an HL7 Segment" )
end
if index.kind_of?(Range) || index.kind_of?(Fixnum)
@segments[ index ] = value
elsif index.respond_to?(:to_sym)
(@segments_by_name[ index.to_sym ] ||= []) << value
else
raise HL7::Exception.new( "attempting to use an indice that is not a Range, Fixnum or to_sym providing object" )
end
value.segment_parent = self
end
# return the index of the value if it exists, nil otherwise
# value:: is expected to be a string
def index( value )
return nil unless (value && value.respond_to?(:to_sym))
segs = @segments_by_name[ value.to_sym ]
return nil unless segs
@segments.index( segs.to_a.first )
end
# add a segment or array of segments to the message
# * will force auto set_id sequencing for segments containing set_id's
def <<( value )
# do nothing if value is nil
return unless value
if value.kind_of? Array
value.map{|item| append(item)}
else
append(value)
end
end
def append( value )
unless ( value && value.kind_of?(HL7::Message::Segment) )
raise HL7::Exception.new( "attempting to append something other than an HL7 Segment" )
end
value.segment_parent = self unless value.segment_parent
(@segments ||= []) << value
name = value.class.to_s.gsub("HL7::Message::Segment::", "").to_sym
(@segments_by_name[ name ] ||= []) << value
sequence_segments unless @parsing # let's auto-set the set-id as we go
end
# yield each segment in the message
def each # :yields: segment
return unless @segments
@segments.each { |s| yield s }
end
# return the segment count
def length
0 unless @segments
@segments.length
end
# provide a screen-readable version of the message
def to_s
@segments.collect { |s| s if s.to_s.length > 0 }.join( "\n" )
end
# provide a HL7 spec version of the message
def to_hl7
@segments.collect { |s| s if s.to_s.length > 0 }.join( @delimiter.segment )
end
# provide the HL7 spec version of the message wrapped in MLLP
def to_mllp
pre_mllp = to_hl7
"\x0b" + pre_mllp + "\x1c\r"
end
# auto-set the set_id fields of any message segments that
# provide it and have more than one instance in the message
def sequence_segments(base=nil)
last = nil
segs = @segments
segs = base.children if base
segs.each do |s|
if s.kind_of?( last.class ) && s.respond_to?( :set_id )
last.set_id = 1 unless last.set_id && last.set_id.to_i > 0
s.set_id = last.set_id.to_i + 1
end
sequence_segments( s ) if s.has_children?
last = s
end
end
private
def generate_segments( ary )
raise HL7::ParseError.new( "no array to generate segments" ) unless ary.length > 0
@parsing = true
last_seg = nil
ary.each do |elm|
if elm.slice(0,3) == "MSH"
update_delimiters(elm)
end
last_seg = generate_segment( elm, last_seg ) || last_seg
end
@parsing = nil
end
def update_delimiters(elm)
@item_delim = message_parser.parse_item_delim(elm)
@element_delim = message_parser.parse_element_delim(elm)
@delimiter.item = @item_delim
@delimiter.element = @element_delim
end
def generate_segment( elm, last_seg )
segment_generator = HL7::Message::SegmentGenerator.new( elm,
last_seg,
@delimiter )
return nil unless segment_generator.valid_segments_parts?
segment_generator.seg_name = segment_generator.seg_parts[0]
new_seg = segment_generator.build
new_seg.segment_parent = self
choose_segment_from(last_seg, new_seg, segment_generator.seg_name)
end
def choose_segment_from(last_seg, new_seg, seg_name)
if last_seg && last_seg.has_children? && last_seg.accepts?( seg_name )
last_seg.children << new_seg
new_seg.is_child_segment = true
last_seg
else
@segments << new_seg
setup_segment_lookup_by_name( seg_name, new_seg)
new_seg
end
end
# Allow segment lookup by name
def setup_segment_lookup_by_name(seg_name, new_seg)
seg_sym = get_symbol_from_name(seg_name)
if seg_sym
@segments_by_name[ seg_sym ] ||= []
@segments_by_name[ seg_sym ] << new_seg
end
end
def get_symbol_from_name(seg_name)
seg_name.to_s.strip.length > 0 ? seg_name.to_sym : nil
end
end