ruby-hl7 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/segment.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# Ruby Object representation of an hl7 2.x message segment
|
2
|
+
# The segments can be setup to provide aliases to specific fields with
|
3
|
+
# optional validation code that is run when the field is modified
|
4
|
+
# The segment field data is also accessible via the e<number> method.
|
5
|
+
#
|
6
|
+
# == Defining a New Segment
|
7
|
+
# class HL7::Message::Segment::NK1 < HL7::Message::Segment
|
8
|
+
# wieght 100 # segments are sorted ascendingly
|
9
|
+
# add_field :something_you_want # assumes :idx=>1
|
10
|
+
# add_field :something_else, :idx=>6 # :idx=>6 and field count=6
|
11
|
+
# add_field :something_more # :idx=>7
|
12
|
+
# add_field :block_example do |value|
|
13
|
+
# raise HL7::InvalidDataError.new
|
14
|
+
# unless value.to_i < 100 && value.to_i > 10
|
15
|
+
# return value
|
16
|
+
# end
|
17
|
+
# # this block will be executed when seg.block_example= is called
|
18
|
+
# # and when seg.block_example is called
|
19
|
+
#
|
20
|
+
class HL7::Message::Segment
|
21
|
+
extend HL7::Message::SegmentListStorage
|
22
|
+
include HL7::Message::SegmentFields
|
23
|
+
|
24
|
+
attr :segment_parent, true
|
25
|
+
attr :element_delim
|
26
|
+
attr :item_delim
|
27
|
+
attr :segment_weight
|
28
|
+
|
29
|
+
METHOD_MISSING_FOR_INITIALIZER = <<-END
|
30
|
+
def method_missing( sym, *args, &blk )
|
31
|
+
__seg__.send( sym, args, blk )
|
32
|
+
end
|
33
|
+
END
|
34
|
+
|
35
|
+
# setup a new HL7::Message::Segment
|
36
|
+
# raw_segment:: is an optional String or Array which will be used as the
|
37
|
+
# segment's field data
|
38
|
+
# delims:: an optional array of delimiters, where
|
39
|
+
# delims[0] = element delimiter
|
40
|
+
# delims[1] = item delimiter
|
41
|
+
def initialize(raw_segment="", delims=[], &blk)
|
42
|
+
@segments_by_name = {}
|
43
|
+
@field_total = 0
|
44
|
+
@is_child = false
|
45
|
+
|
46
|
+
setup_delimiters delims
|
47
|
+
|
48
|
+
@elements = elements_from_segment(raw_segment)
|
49
|
+
|
50
|
+
if block_given?
|
51
|
+
callctx = eval( "self", blk.binding )
|
52
|
+
def callctx.__seg__(val=nil)
|
53
|
+
@__seg_val__ ||= val
|
54
|
+
end
|
55
|
+
callctx.__seg__(self)
|
56
|
+
# TODO: find out if this pollutes the calling namespace permanently...
|
57
|
+
|
58
|
+
eval( METHOD_MISSING_FOR_INITIALIZER, blk.binding )
|
59
|
+
yield self
|
60
|
+
eval( "class << self; remove_method :method_missing;end", blk.binding )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Breaks the raw segment into elements
|
65
|
+
# raw_segment:: is an optional String or Array which will be used as the
|
66
|
+
# segment's field data
|
67
|
+
def elements_from_segment(raw_segment)
|
68
|
+
if (raw_segment.kind_of? Array)
|
69
|
+
elements = raw_segment
|
70
|
+
else
|
71
|
+
elements = HL7::MessageParser.split_by_delimiter( raw_segment,
|
72
|
+
@element_delim )
|
73
|
+
if raw_segment == ""
|
74
|
+
elements[0] = self.class.to_s.split( "::" ).last
|
75
|
+
elements << ""
|
76
|
+
end
|
77
|
+
end
|
78
|
+
elements
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_info
|
82
|
+
"%s: empty segment >> %s" % [ self.class.to_s, @elements.inspect ]
|
83
|
+
end
|
84
|
+
|
85
|
+
# output the HL7 spec version of the segment
|
86
|
+
def to_s
|
87
|
+
@elements.join( @element_delim )
|
88
|
+
end
|
89
|
+
|
90
|
+
# at the segment level there is no difference between to_s and to_hl7
|
91
|
+
alias :to_hl7 :to_s
|
92
|
+
|
93
|
+
# handle the e<number> field accessor
|
94
|
+
# and any aliases that didn't get added to the system automatically
|
95
|
+
def method_missing( sym, *args, &blk )
|
96
|
+
base_str = sym.to_s.gsub( "=", "" )
|
97
|
+
base_sym = base_str.to_sym
|
98
|
+
|
99
|
+
if self.class.fields.include?( base_sym )
|
100
|
+
# base_sym is ok, let's move on
|
101
|
+
elsif /e([0-9]+)/.match( base_str )
|
102
|
+
# base_sym should actually be $1, since we're going by
|
103
|
+
# element id number
|
104
|
+
base_sym = $1.to_i
|
105
|
+
else
|
106
|
+
super
|
107
|
+
end
|
108
|
+
|
109
|
+
if sym.to_s.include?( "=" )
|
110
|
+
write_field( base_sym, args )
|
111
|
+
else
|
112
|
+
if args.length > 0
|
113
|
+
write_field( base_sym, args.flatten.select { |arg| arg } )
|
114
|
+
else
|
115
|
+
read_field( base_sym )
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# sort-compare two Segments, 0 indicates equality
|
121
|
+
def <=>( other )
|
122
|
+
return nil unless other.kind_of?(HL7::Message::Segment)
|
123
|
+
|
124
|
+
# per Comparable docs: http://www.ruby-doc.org/core/classes/Comparable.html
|
125
|
+
diff = self.weight - other.weight
|
126
|
+
return -1 if diff > 0
|
127
|
+
return 1 if diff < 0
|
128
|
+
return 0
|
129
|
+
end
|
130
|
+
|
131
|
+
# get the defined sort-weight of this segment class
|
132
|
+
# an alias for self.weight
|
133
|
+
def weight
|
134
|
+
self.class.weight
|
135
|
+
end
|
136
|
+
|
137
|
+
# return true if the segment has a parent
|
138
|
+
def is_child_segment?
|
139
|
+
(@is_child_segment ||= false)
|
140
|
+
end
|
141
|
+
|
142
|
+
# indicate whether or not the segment has a parent
|
143
|
+
def is_child_segment=(val)
|
144
|
+
@is_child_segment = val
|
145
|
+
end
|
146
|
+
|
147
|
+
# get the length of the segment (number of fields it contains)
|
148
|
+
def length
|
149
|
+
0 unless @elements
|
150
|
+
@elements.length
|
151
|
+
end
|
152
|
+
|
153
|
+
def has_children?
|
154
|
+
self.respond_to?(:children)
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
def self.singleton #:nodoc:
|
159
|
+
class << self; self end
|
160
|
+
end
|
161
|
+
|
162
|
+
def setup_delimiters(delims)
|
163
|
+
delims = [ delims ].flatten
|
164
|
+
|
165
|
+
@element_delim = ( delims.length>0 ) ? delims[0] : "|"
|
166
|
+
@item_delim = ( delims.length>1 ) ? delims[1] : "^"
|
167
|
+
end
|
168
|
+
|
169
|
+
# DSL element to define a segment's sort weight
|
170
|
+
# returns the segment's current weight by default
|
171
|
+
# segments are sorted ascending
|
172
|
+
def self.weight(new_weight=nil)
|
173
|
+
if new_weight
|
174
|
+
singleton.module_eval do
|
175
|
+
@my_weight = new_weight
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
singleton.module_eval do
|
180
|
+
return 999 unless @my_weight
|
181
|
+
@my_weight
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.convert_to_ts(value) #:nodoc:
|
186
|
+
value.respond_to?(:to_hl7) ? value.to_hl7 : value
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Provide a catch-all information preserving segment
|
2
|
+
# * nb: aliases are not provided BUT you can use the numeric element accessor
|
3
|
+
#
|
4
|
+
# seg = HL7::Message::Segment::Default.new
|
5
|
+
# seg.e0 = "NK1"
|
6
|
+
# seg.e1 = "SOMETHING ELSE"
|
7
|
+
# seg.e2 = "KIN HERE"
|
8
|
+
#
|
9
|
+
class HL7::Message::Segment::Default < HL7::Message::Segment
|
10
|
+
def initialize(raw_segment="", delims=[])
|
11
|
+
segs = [] if (raw_segment == "")
|
12
|
+
segs ||= raw_segment
|
13
|
+
super( segs, delims )
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# load our segments
|
18
|
+
Dir["#{File.dirname(__FILE__)}/segments/*.rb"].each { |ext| load ext }
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# SegmentFields
|
2
|
+
# class HL7::Message::Segment::NK1 < HL7::Message::Segment
|
3
|
+
# wieght 100 # segments are sorted ascendingly
|
4
|
+
# add_field :something_you_want # assumes :idx=>1
|
5
|
+
# add_field :something_else, :idx=>6 # :idx=>6 and field count=6
|
6
|
+
module HL7::Message::SegmentFields
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
@elements = []
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# define a field alias
|
15
|
+
# * name is the alias itself (required)
|
16
|
+
# * options is a hash of parameters
|
17
|
+
# * :id is the field number to reference (optional, auto-increments from 1
|
18
|
+
# by default)
|
19
|
+
# * :blk is a validation proc (optional, overrides the second argument)
|
20
|
+
# * blk is an optional validation/convertion proc which MUST
|
21
|
+
# take a parameter and always return a value for the field (it will be
|
22
|
+
# used on read/write calls)
|
23
|
+
def add_field( name, options={}, &blk )
|
24
|
+
options = { :idx =>-1, :blk =>blk}.merge!( options )
|
25
|
+
name ||= :id
|
26
|
+
namesym = name.to_sym
|
27
|
+
@field_cnt ||= 1
|
28
|
+
if options[:idx] == -1
|
29
|
+
options[:idx] = @field_cnt # provide default auto-incrementing
|
30
|
+
end
|
31
|
+
@field_cnt = options[:idx].to_i + 1
|
32
|
+
|
33
|
+
singleton.module_eval do
|
34
|
+
@fields ||= {}
|
35
|
+
@fields[ namesym ] = options
|
36
|
+
end
|
37
|
+
|
38
|
+
self.class_eval <<-END
|
39
|
+
def #{name}(val=nil)
|
40
|
+
unless val
|
41
|
+
read_field( :#{namesym} )
|
42
|
+
else
|
43
|
+
write_field( :#{namesym}, val )
|
44
|
+
val # this matches existing n= method functionality
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def #{name}=(value)
|
49
|
+
write_field( :#{namesym}, value )
|
50
|
+
end
|
51
|
+
END
|
52
|
+
end
|
53
|
+
|
54
|
+
def fields #:nodoc:
|
55
|
+
singleton.module_eval do
|
56
|
+
(@fields ||= [])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def alias_field(new_field_name, old_field_name)
|
61
|
+
self.class_eval <<-END
|
62
|
+
def #{new_field_name}(val=nil)
|
63
|
+
raise HL7::InvalidDataError.new unless self.class.fields[:#{old_field_name}]
|
64
|
+
unless val
|
65
|
+
read_field( :#{old_field_name} )
|
66
|
+
else
|
67
|
+
write_field( :#{old_field_name}, val )
|
68
|
+
val # this matches existing n= method functionality
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def #{new_field_name}=(value)
|
73
|
+
write_field( :#{old_field_name}, value )
|
74
|
+
end
|
75
|
+
END
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def field_info( name ) #:nodoc:
|
80
|
+
field_blk = nil
|
81
|
+
idx = name # assume we've gotten a fixnum
|
82
|
+
unless name.kind_of?( Fixnum )
|
83
|
+
fld_info = self.class.fields[ name ]
|
84
|
+
idx = fld_info[:idx].to_i
|
85
|
+
field_blk = fld_info[:blk]
|
86
|
+
end
|
87
|
+
|
88
|
+
[ idx, field_blk ]
|
89
|
+
end
|
90
|
+
|
91
|
+
def read_field( name ) #:nodoc:
|
92
|
+
idx, field_blk = field_info( name )
|
93
|
+
return nil unless idx
|
94
|
+
return nil if (idx >= @elements.length)
|
95
|
+
|
96
|
+
ret = @elements[ idx ]
|
97
|
+
ret = ret.first if (ret.kind_of?(Array) && ret.length == 1)
|
98
|
+
ret = field_blk.call( ret ) if field_blk
|
99
|
+
ret
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_field( name, value ) #:nodoc:
|
103
|
+
idx, field_blk = field_info( name )
|
104
|
+
return nil unless idx
|
105
|
+
|
106
|
+
if (idx >= @elements.length)
|
107
|
+
# make some space for the incoming field, missing items are assumed to
|
108
|
+
# be empty, so this is valid per the spec -mg
|
109
|
+
missing = ("," * (idx-@elements.length)).split(',',-1)
|
110
|
+
@elements += missing
|
111
|
+
end
|
112
|
+
|
113
|
+
value = value.first if (value && value.kind_of?(Array) && value.length == 1)
|
114
|
+
value = field_blk.call( value ) if field_blk
|
115
|
+
@elements[ idx ] = value.to_s
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Class for grouping the messages delimiter
|
2
|
+
class HL7::Message::Delimiter
|
3
|
+
attr_accessor :item, :element, :segment
|
4
|
+
|
5
|
+
def initialize(element_delim, item_delim, segment_delim)
|
6
|
+
@element = element_delim
|
7
|
+
@item = item_delim
|
8
|
+
@segment = segment_delim
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Methods for creating segments in Message
|
13
|
+
class HL7::Message::SegmentGenerator
|
14
|
+
|
15
|
+
attr_reader :element, :last_seg
|
16
|
+
attr_reader :delimiter
|
17
|
+
|
18
|
+
attr_accessor :seg_parts, :seg_name
|
19
|
+
|
20
|
+
def initialize(element, last_seg, delimiter)
|
21
|
+
@element = element
|
22
|
+
@last_seg = last_seg
|
23
|
+
@delimiter = delimiter
|
24
|
+
|
25
|
+
@seg_parts = HL7::MessageParser.split_by_delimiter( element,
|
26
|
+
delimiter.element )
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid_segments_parts?
|
30
|
+
return true if @seg_parts && @seg_parts.length > 0
|
31
|
+
|
32
|
+
if HL7.ParserConfig[:empty_segment_is_error]
|
33
|
+
raise HL7::EmptySegmentNotAllowed
|
34
|
+
else
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def build
|
40
|
+
klass = get_segment_class
|
41
|
+
new_seg = klass.new( @element, [@delimiter.element, @delimiter.item] )
|
42
|
+
new_seg
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_segment_class
|
46
|
+
segment_to_search = @seg_name.to_sym
|
47
|
+
segment_to_search = @seg_name if RUBY_VERSION < "1.9"
|
48
|
+
|
49
|
+
if HL7::Message::Segment.constants.index(segment_to_search)
|
50
|
+
eval("HL7::Message::Segment::%s" % @seg_name)
|
51
|
+
else
|
52
|
+
HL7::Message::Segment::Default
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# This module includes methods for storing segments inside segments.
|
2
|
+
# has_children(child_types) defines three methods dynamically.
|
3
|
+
|
4
|
+
module HL7::Message::SegmentListStorage
|
5
|
+
attr_reader :child_types
|
6
|
+
|
7
|
+
def add_child_type(child_type)
|
8
|
+
if @child_types
|
9
|
+
@child_types << child_type.to_sym
|
10
|
+
else
|
11
|
+
has_children [ child_type.to_sym ]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
# allows a segment to store other segment objects
|
17
|
+
# used to handle associated lists like one OBR to many OBX segments
|
18
|
+
def has_children(child_types)
|
19
|
+
@child_types = child_types
|
20
|
+
|
21
|
+
define_method_child_types
|
22
|
+
define_method_children
|
23
|
+
define_method_accepts
|
24
|
+
end
|
25
|
+
|
26
|
+
def define_method_child_types
|
27
|
+
define_method(:child_types) do
|
28
|
+
self.class.child_types
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def define_method_accepts
|
33
|
+
self.class_eval do
|
34
|
+
define_method('accepts?') do |t|
|
35
|
+
t = t.to_sym if t.respond_to?(:to_sym)
|
36
|
+
!!child_types.index(t)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def define_method_children
|
42
|
+
self.class_eval do
|
43
|
+
define_method(:children) do
|
44
|
+
unless @my_children
|
45
|
+
p = self
|
46
|
+
@my_children ||= []
|
47
|
+
@my_children.instance_eval do
|
48
|
+
@parental = p
|
49
|
+
alias :old_append :<<
|
50
|
+
|
51
|
+
def <<( value )
|
52
|
+
# do nothing if value is nil
|
53
|
+
return unless value
|
54
|
+
|
55
|
+
# make sure it's an array
|
56
|
+
value = [value].flatten
|
57
|
+
value.map{|item| append(item)}
|
58
|
+
end
|
59
|
+
|
60
|
+
def append(value)
|
61
|
+
unless (value && value.kind_of?(HL7::Message::Segment))
|
62
|
+
raise HL7::Exception.new( "attempting to append non-segment to a segment list" )
|
63
|
+
end
|
64
|
+
|
65
|
+
value.segment_parent = @parental
|
66
|
+
k = @parental
|
67
|
+
while (k && k.segment_parent && !k.segment_parent.kind_of?(HL7::Message))
|
68
|
+
k = k.segment_parent
|
69
|
+
end
|
70
|
+
k.segment_parent << value if k && k.segment_parent
|
71
|
+
old_append( value )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
@my_children
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|