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
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
|