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