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