rtp-connect 1.6 → 1.11

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.
@@ -9,7 +9,7 @@ module RTP
9
9
  class Prescription < Record
10
10
 
11
11
  # The Record which this instance belongs to.
12
- attr_reader :parent
12
+ attr_accessor :parent
13
13
  # The SiteSetup record (if any) that belongs to this Prescription.
14
14
  attr_reader :site_setup
15
15
  # An array of SimulationField records (if any) that belongs to this Prescription.
@@ -36,28 +36,8 @@ module RTP
36
36
  # @raise [ArgumentError] if given a string containing an invalid number of elements
37
37
  #
38
38
  def self.load(string, parent)
39
- # Get the quote-less values:
40
- values = string.to_s.values
41
- low_limit = 4
42
- high_limit = 13
43
- raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
44
- RTP.logger.warn "The number of elements (#{values.length}) for this Prescription record exceeds the known number of data items for this record (#{high_limit}). This may indicate an invalid record or that the RTP format has recently been expanded with new items." if values.length > high_limit
45
39
  p = self.new(parent)
46
- # Assign the values to attributes:
47
- p.keyword = values[0]
48
- p.course_id = values[1]
49
- p.rx_site_name = values[2]
50
- p.technique = values[3]
51
- p.modality = values[4]
52
- p.dose_spec = values[5]
53
- p.rx_depth = values[6]
54
- p.dose_ttl = values[7]
55
- p.dose_tx = values[8]
56
- p.pattern = values[9]
57
- p.rx_note = values[10]
58
- p.number_of_fields = values[11]
59
- p.crc = values[-1]
60
- return p
40
+ p.load(string)
61
41
  end
62
42
 
63
43
  # Creates a new Prescription site.
@@ -65,6 +45,7 @@ module RTP
65
45
  # @param [Record] parent a record which is used to determine the proper parent of this instance
66
46
  #
67
47
  def initialize(parent)
48
+ super('RX_DEF', 4, 13)
68
49
  # Child objects:
69
50
  @site_setup = nil
70
51
  @fields = Array.new
@@ -72,7 +53,22 @@ module RTP
72
53
  # Parent relation (may get more than one type of record here):
73
54
  @parent = get_parent(parent.to_record, Plan)
74
55
  @parent.add_prescription(self)
75
- @keyword = 'RX_DEF'
56
+ @attributes = [
57
+ # Required:
58
+ :keyword,
59
+ :course_id,
60
+ :rx_site_name,
61
+ # Optional:
62
+ :technique,
63
+ :modality,
64
+ :dose_spec,
65
+ :rx_depth,
66
+ :dose_ttl,
67
+ :dose_tx,
68
+ :pattern,
69
+ :rx_note,
70
+ :number_of_fields
71
+ ]
76
72
  end
77
73
 
78
74
  # Checks for equality.
@@ -97,6 +93,7 @@ module RTP
97
93
  #
98
94
  def add_field(child)
99
95
  @fields << child.to_field
96
+ child.parent = self
100
97
  end
101
98
 
102
99
  # Adds a simulation field record to this instance.
@@ -105,6 +102,7 @@ module RTP
105
102
  #
106
103
  def add_simulation_field(child)
107
104
  @simulation_fields << child.to_simulation_field
105
+ child.parent = self
108
106
  end
109
107
 
110
108
  # Adds a site setup record to this instance.
@@ -113,6 +111,7 @@ module RTP
113
111
  #
114
112
  def add_site_setup(child)
115
113
  @site_setup = child.to_site_setup
114
+ child.parent = self
116
115
  end
117
116
 
118
117
  # Collects the child records of this instance in a properly sorted array.
@@ -123,73 +122,57 @@ module RTP
123
122
  return [@site_setup, @simulation_fields, @fields].flatten.compact
124
123
  end
125
124
 
126
- # Computes a hash code for this object.
125
+ # Removes the reference of the given instance from this instance.
127
126
  #
128
- # @note Two objects with the same attributes will have the same hash code.
127
+ # @param [Field, SimulationField, SiteSetup] record a child record to be removed from this instance
129
128
  #
130
- # @return [Fixnum] the object's hash code
131
- #
132
- def hash
133
- state.hash
129
+ def delete(record)
130
+ case record
131
+ when Field
132
+ delete_child(:fields, record)
133
+ when SimulationField
134
+ delete_child(:simulation_fields, record)
135
+ when SiteSetup
136
+ delete_site_setup
137
+ else
138
+ logger.warn("Unknown class (record) given to Prescription#delete: #{record.class}")
139
+ end
134
140
  end
135
141
 
136
- # Collects the values (attributes) of this instance.
137
- #
138
- # @note The CRC is not considered part of the actual values and is excluded.
139
- # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
140
- #
141
- def values
142
- return [
143
- @keyword,
144
- @course_id,
145
- @rx_site_name,
146
- @technique,
147
- @modality,
148
- @dose_spec,
149
- @rx_depth,
150
- @dose_ttl,
151
- @dose_tx,
152
- @pattern,
153
- @rx_note,
154
- @number_of_fields
155
- ]
142
+ # Removes all field references from this instance.
143
+ #
144
+ def delete_fields
145
+ delete_children(:fields)
156
146
  end
157
147
 
158
- # Returns self.
148
+ # Removes all simulation_field references from this instance.
159
149
  #
160
- # @return [Prescription] self
150
+ def delete_simulation_fields
151
+ delete_children(:simulation_fields)
152
+ end
153
+
154
+ # Removes the site setup reference from this instance.
161
155
  #
162
- def to_prescription
163
- self
156
+ def delete_site_setup
157
+ delete_child(:site_setup)
164
158
  end
165
159
 
166
- # Encodes the Prescription object + any hiearchy of child objects,
167
- # to a properly formatted RTPConnect ascii string.
160
+ # Computes a hash code for this object.
168
161
  #
169
- # @return [String] an RTP string with a single or multiple lines/records
162
+ # @note Two objects with the same attributes will have the same hash code.
170
163
  #
171
- def to_s
172
- str = encode
173
- if children
174
- children.each do |child|
175
- str += child.to_s
176
- end
177
- end
178
- return str
164
+ # @return [Fixnum] the object's hash code
165
+ #
166
+ def hash
167
+ state.hash
179
168
  end
180
169
 
181
- alias :to_str :to_s
182
-
183
- # Sets the keyword attribute.
170
+ # Returns self.
184
171
  #
185
- # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
186
- # @param [#to_s] value the new attribute value
187
- # @raise [ArgumentError] if given an unexpected keyword
172
+ # @return [Prescription] self
188
173
  #
189
- def keyword=(value)
190
- value = value.to_s.upcase
191
- raise ArgumentError, "Invalid keyword. Expected 'RX_DEF', got #{value}." unless value == "RX_DEF"
192
- @keyword = value
174
+ def to_prescription
175
+ self
193
176
  end
194
177
 
195
178
  # Sets the course_id attribute.
@@ -1,58 +1,226 @@
1
- module RTP
2
-
3
- # The Record class contains attributes and methods that are common
4
- # for the various record types defined in the RTPConnect standard.
5
- #
6
- class Record
7
-
8
- # The keyword defines the record type of a particular RTP string line.
9
- attr_reader :keyword
10
- # The CRC is used to validate the integrity of the content of the RTP string line.
11
- attr_reader :crc
12
-
13
- # Sets the crc (checksum) attribute.
14
- #
15
- # @note This value is not used when creating an RTP string from a record (a new crc is calculated)
16
- # @param [#to_s] value the new attribute value
17
- #
18
- def crc=(value)
19
- @crc = value.to_s
20
- end
21
-
22
- # Encodes a string from the contents of this instance.
23
- #
24
- # This produces the full record string line, including a computed CRC checksum.
25
- #
26
- # @return [String] a proper RTPConnect type CSV string
27
- #
28
- def encode
29
- content = CSV.generate_line(values, force_quotes: true, row_sep: '') + ","
30
- checksum = content.checksum
31
- # Complete string is content + checksum (in double quotes) + carriage return + line feed
32
- return (content + checksum.to_s.wrap + "\r\n").encode('ISO8859-1')
33
- end
34
-
35
- # Follows the tree of parents until the appropriate parent of the requesting record is found.
36
- #
37
- # @param [Record] last_parent the previous parent (the record from the previous line in the RTP file)
38
- # @param [Record] klass the expected parent record class of this record (e.g. Plan, Field)
39
- #
40
- def get_parent(last_parent, klass)
41
- if last_parent.is_a?(klass)
42
- return last_parent
43
- else
44
- return last_parent.get_parent(last_parent.parent, klass)
45
- end
46
- end
47
-
48
- # Returns self.
49
- #
50
- # @return [Record] self
51
- #
52
- def to_record
53
- self
54
- end
55
-
56
- end
57
-
1
+ module RTP
2
+
3
+ # The Record class contains attributes and methods that are common
4
+ # for the various record types defined in the RTPConnect standard.
5
+ #
6
+ class Record
7
+
8
+ # For most record types, there are no surplus (grouped) attributes.
9
+ NR_SURPLUS_ATTRIBUTES = 0
10
+
11
+ # An array of the record's attributes.
12
+ attr_reader :attributes
13
+ # The keyword defines the record type of a particular RTP string line.
14
+ attr_reader :keyword
15
+ # The CRC is used to validate the integrity of the content of the RTP string line.
16
+ attr_reader :crc
17
+
18
+ # Creates a new Record.
19
+ #
20
+ # @param [String] keyword the keyword which identifies this record
21
+ # @param [Integer] min_elements the minimum number of data elements required for this record
22
+ # @param [Integer] max_elements the maximum supported number of data elements for this record
23
+ #
24
+ def initialize(keyword, min_elements, max_elements)
25
+ @keyword = keyword
26
+ @min_elements = min_elements
27
+ @max_elements = max_elements
28
+ end
29
+
30
+ # Sets the crc (checksum) attribute.
31
+ #
32
+ # @note This value is not used when creating an RTP string from a record (a new crc is calculated)
33
+ # @param [#to_s] value the new attribute value
34
+ #
35
+ def crc=(value)
36
+ @crc = value.to_s
37
+ end
38
+
39
+ # Encodes a string from the contents of this instance.
40
+ #
41
+ # This produces the full record string line, including a computed CRC checksum.
42
+ #
43
+ # @param [Hash] options an optional hash parameter
44
+ # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
45
+ # @return [String] a proper RTPConnect type CSV string
46
+ #
47
+ def encode(options={})
48
+ encoded_values = values.collect {|v| v && v.encode('ISO8859-1')}
49
+ encoded_values = discard_unsupported_attributes(encoded_values, options) if options[:version]
50
+ content = CSV.generate_line(encoded_values, force_quotes: true, row_sep: '') + ","
51
+ checksum = content.checksum
52
+ # Complete string is content + checksum (in double quotes) + carriage return + line feed
53
+ return (content + checksum.to_s.wrap + "\r\n").encode('ISO8859-1')
54
+ end
55
+
56
+ # Follows the tree of parents until the appropriate parent of the requesting record is found.
57
+ #
58
+ # @param [Record] last_parent the previous parent (the record from the previous line in the RTP file)
59
+ # @param [Record] klass the expected parent record class of this record (e.g. Plan, Field)
60
+ #
61
+ def get_parent(last_parent, klass)
62
+ if last_parent.is_a?(klass)
63
+ return last_parent
64
+ else
65
+ return last_parent.get_parent(last_parent.parent, klass)
66
+ end
67
+ end
68
+
69
+ # Verifies a proposed keyword attribute.
70
+ #
71
+ # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method.
72
+ # @param [#to_s] value the proposed keyword attribute
73
+ # @raise [ArgumentError] if given an unexpected keyword
74
+ #
75
+ def keyword=(value)
76
+ value = value.to_s.upcase
77
+ raise ArgumentError, "Invalid keyword. Expected '#{@keyword}', got #{value}." unless value == @keyword
78
+ end
79
+
80
+ # Sets up a record by parsing a RTPConnect string line.
81
+ #
82
+ # @param [#to_s] string the extended treatment field definition record string line
83
+ # @return [Record] the updated Record instance
84
+ # @raise [ArgumentError] if given a string containing an invalid number of elements
85
+ #
86
+ def load(string, options={})
87
+ # Extract processed values:
88
+ values = string.to_s.values(options[:repair])
89
+ raise ArgumentError, "Invalid argument 'string': Expected at least #{@min_elements} elements for #{@keyword}, got #{values.length}." if values.length < @min_elements
90
+ RTP.logger.warn "The number of given elements (#{values.length}) exceeds the known number of data elements for this record (#{@max_elements}). This may indicate an invalid string record or that the RTP format has recently been expanded with new elements." if values.length > @max_elements
91
+ self.send(:set_attributes, values)
92
+ self
93
+ end
94
+
95
+ # Returns self.
96
+ #
97
+ # @return [Record] self
98
+ #
99
+ def to_record
100
+ self
101
+ end
102
+
103
+ # Encodes the record + any hiearchy of child objects,
104
+ # to a properly formatted RTPConnect ascii string.
105
+ #
106
+ # @param [Hash] options an optional hash parameter
107
+ # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
108
+ # @return [String] an RTP string with a single or multiple lines/records
109
+ #
110
+ def to_s(options={})
111
+ str = encode(options)
112
+ children.each do |child|
113
+ # Note that the extended plan record was introduced in Mosaiq 2.5.
114
+ str += child.to_s(options) unless child.class == ExtendedPlan && options[:version].to_f < 2.5
115
+ end
116
+ str
117
+ end
118
+
119
+ alias :to_str :to_s
120
+
121
+ # Collects the values (attributes) of this instance.
122
+ #
123
+ # @note The CRC is not considered part of the actual values and is excluded.
124
+ # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
125
+ #
126
+ def values
127
+ @attributes.collect {|attribute| self.send(attribute)}
128
+ end
129
+
130
+
131
+ private
132
+
133
+
134
+ # Removes the reference of the given instance from the attribute of this record.
135
+ #
136
+ # @param [Symbol] attribute the name of the child attribute from which to remove a child
137
+ # @param [Record] instance a child record to be removed from this instance
138
+ #
139
+ def delete_child(attribute, instance=nil)
140
+ if self.send(attribute).is_a?(Array)
141
+ deleted = self.send(attribute).delete(instance)
142
+ deleted.parent = nil if deleted
143
+ else
144
+ self.send(attribute).parent = nil if self.send(attribute)
145
+ self.instance_variable_set("@#{attribute}", nil)
146
+ end
147
+ end
148
+
149
+ # Removes all child references of the given type from this instance.
150
+ #
151
+ # @param [Symbol] attribute the name of the child attribute to be cleared
152
+ #
153
+ def delete_children(attribute)
154
+ self.send(attribute).each { |c| c.parent = nil }
155
+ self.send(attribute).clear
156
+ end
157
+
158
+ # Sets the attributes of the record instance.
159
+ #
160
+ # @param [Array<String>] values the record attributes (as parsed from a record string)
161
+ #
162
+ def set_attributes(values)
163
+ import_indices([values.length - 1, @max_elements - 1].min).each_with_index do |indices, i|
164
+ param = nil
165
+ if indices
166
+ param = values.values_at(*indices)
167
+ param = param[0] if param.length == 1
168
+ end
169
+ self.send("#{@attributes[i]}=", param)
170
+ end
171
+ @crc = values[-1]
172
+ end
173
+
174
+ # Gives an array of indices indicating where the attributes of this record gets its
175
+ # values from in the comma separated string which the instance is created from.
176
+ #
177
+ # @param [Integer] length the number of elements to create in the indices array
178
+ #
179
+ def import_indices(length)
180
+ Array.new(length - NR_SURPLUS_ATTRIBUTES) { |i| [i] }
181
+ end
182
+
183
+ # Removes any attributes that are newer than the given compatibility target version.
184
+ # E.g. if a compatibility version of Mosaiq 2.4 is specified, attributes that were
185
+ # introduced in Mosaiq 2.5 or later is removed before the RTP string is created.
186
+ #
187
+ # @param [Array<String>] values the complete set of values of this record
188
+ # @param [Hash] options an optional hash parameter
189
+ # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
190
+ # @return [Array<String>] an array of attributes where some of the recent attributes may have been removed
191
+ #
192
+ def discard_unsupported_attributes(values, options={})
193
+ ver = options[:version].to_f
194
+ case self
195
+ when SiteSetup
196
+ if ver >= 2.81
197
+ values
198
+ elsif ver >= 2.65
199
+ values[0..-3]
200
+ elsif ver >= 2.6
201
+ values[0..-7]
202
+ else
203
+ values[0..-10]
204
+ end
205
+ when Field
206
+ ver >= 2.64 ? values : values[0..-4]
207
+ when ExtendedField
208
+ if ver >= 2.65
209
+ values
210
+ elsif ver >= 2.4
211
+ values[0..-3]
212
+ else
213
+ values[0..-7]
214
+ end
215
+ when ControlPoint
216
+ ver >= 2.64 ? values : values[0..31] + values[35..-1]
217
+ when ExtendedPlan
218
+ ver >= 2.81 ? values : values[0..-2]
219
+ else
220
+ values
221
+ end
222
+ end
223
+
224
+ end
225
+
58
226
  end