rtp-connect 1.8 → 1.9

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 Field < Record
10
10
 
11
11
  # The Record which this instance belongs to.
12
- attr_reader :parent
12
+ attr_accessor :parent
13
13
  # The ExtendedField record (if any) that belongs to this Field.
14
14
  attr_reader :extended_field
15
15
  # An array of ControlPoint records (if any) that belongs to this Field.
@@ -162,6 +162,7 @@ module RTP
162
162
  #
163
163
  def add_control_point(child)
164
164
  @control_points << child.to_control_point
165
+ child.parent = self
165
166
  end
166
167
 
167
168
  # Adds an extended treatment field record to this instance.
@@ -170,6 +171,7 @@ module RTP
170
171
  #
171
172
  def add_extended_field(child)
172
173
  @extended_field = child.to_extended_field
174
+ child.parent = self
173
175
  end
174
176
 
175
177
  # Collects the child records of this instance in a properly sorted array.
@@ -212,6 +214,33 @@ module RTP
212
214
  value = @collimator_y2.to_f * 10
213
215
  end
214
216
 
217
+ # Removes the reference of the given instance from this instance.
218
+ #
219
+ # @param [ControlPoint, ExtendedField] record a child record to be removed from this instance
220
+ #
221
+ def delete(record)
222
+ case record
223
+ when ControlPoint
224
+ delete_child(:control_points, record)
225
+ when ExtendedField
226
+ delete_extended_field
227
+ else
228
+ logger.warn("Unknown class (record) given to Field#delete: #{record.class}")
229
+ end
230
+ end
231
+
232
+ # Removes all control point references from this instance.
233
+ #
234
+ def delete_control_points
235
+ delete_children(:control_points)
236
+ end
237
+
238
+ # Removes the extended field reference from this instance.
239
+ #
240
+ def delete_extended_field
241
+ delete_child(:extended_field)
242
+ end
243
+
215
244
  # Computes a hash code for this object.
216
245
  #
217
246
  # @note Two objects with the same attributes will have the same hash code.
@@ -230,25 +259,6 @@ module RTP
230
259
  self
231
260
  end
232
261
 
233
- # Encodes the Field object + any hiearchy of child objects,
234
- # to a properly formatted RTPConnect ascii string.
235
- #
236
- # @param [Hash] options an optional hash parameter
237
- # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
238
- # @return [String] an RTP string with a single or multiple lines/records
239
- #
240
- def to_s(options={})
241
- str = encode(options)
242
- if children
243
- children.each do |child|
244
- str += child.to_s(options)
245
- end
246
- end
247
- return str
248
- end
249
-
250
- alias :to_str :to_s
251
-
252
262
  # Sets the rx_site_name attribute.
253
263
  #
254
264
  # @param [nil, #to_s] value the new attribute value
@@ -1,78 +1,86 @@
1
- module RTP
2
-
3
- class << self
4
-
5
- #--
6
- # Module methods:
7
- #++
8
-
9
- # Gives an array of MLC leaf position boundaries for a given type of MLC,
10
- # specified by its number of leaves at one side.
11
- #
12
- # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
13
- # @return [Array<Fixnum>] the leaf boundary positions
14
- # @raise [ArgumentError] if an unsupported MLC (nr of leaves) is given
15
- #
16
- def leaf_boundaries(nr_leaves)
17
- case nr_leaves
18
- when 29
19
- leaf_boundaries_odd(29)
20
- when 40
21
- leaf_boundaries_even(40)
22
- when 41
23
- leaf_boundaries_odd(41)
24
- when 60
25
- Array.new(10) {|i| (i * 10 - 200).to_i}
26
- .concat(Array.new(41) {|i| (i * 5 - 100).to_i})
27
- .concat(Array.new(10) {|i| (i * 10 + 110).to_i})
28
- when 80
29
- leaf_boundaries_even(80)
30
- else
31
- raise ArgumentError, "Unsupported number of leaves: #{nr_leaves}"
32
- end
33
- end
34
-
35
- # Gives an array of MLC leaf position boundaries for ordinary even numbered
36
- # multi leaf collimators.
37
- #
38
- # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
39
- # @return [Array<Fixnum>] the leaf boundary positions
40
- #
41
- def leaf_boundaries_even(nr_leaves)
42
- Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
43
- end
44
-
45
- # Gives an array of MLC leaf position boundaries for ordinary odd numbered
46
- # multi leaf collimators.
47
- #
48
- # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
49
- # @return [Array<Fixnum>] the leaf boundary positions
50
- #
51
- def leaf_boundaries_odd(nr_leaves)
52
- Array.new(nr_leaves-1) {|i| (10 * (i - (0.5 * nr_leaves - 1))).to_i}.unshift(-200).push(200)
53
- end
54
-
55
- # Computes the CRC checksum of the given line and verifies that
56
- # this value corresponds with the checksum given at the end of the line.
57
- #
58
- # @param [String] line a single line string from an RTPConnect ascii file
59
- # @param [Hash] options the options to use for verifying the RTP record
60
- # @option options [Boolean] :ignore_crc if true, the verification method will return true even if the checksum is invalid
61
- # @return [Boolean] true
62
- # @raise [ArgumentError] if an invalid line/record is given or the string contains an invalid checksum
63
- #
64
- def verify(line, options={})
65
- last_comma_pos = line.rindex(',')
66
- raise ArgumentError, "Invalid line encountered; No comma present in the string: #{line}" unless last_comma_pos
67
- string_to_check = line[0..last_comma_pos]
68
- string_remaining = line[(last_comma_pos+1)..-1]
69
- raise ArgumentError, "Invalid line encountered; Valid checksum missing at end of string: #{string_remaining}" unless string_remaining.length >= 3
70
- checksum_extracted = string_remaining.value.to_i
71
- checksum_computed = string_to_check.checksum
72
- raise ArgumentError, "Invalid line encountered: Specified checksum #{checksum_extracted} deviates from the computed checksum #{checksum_computed}." if checksum_extracted != checksum_computed && !options[:ignore_crc]
73
- return true
74
- end
75
-
76
- end
77
-
1
+ module RTP
2
+
3
+ class << self
4
+
5
+ #--
6
+ # Module methods:
7
+ #++
8
+
9
+ # Gives an array of MLC leaf position boundaries for a given type of MLC,
10
+ # specified by its number of leaves at one side.
11
+ #
12
+ # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
13
+ # @return [Array<Fixnum>] the leaf boundary positions
14
+ # @raise [ArgumentError] if an unsupported MLC (nr of leaves) is given
15
+ #
16
+ def leaf_boundaries(nr_leaves)
17
+ case nr_leaves
18
+ when 29
19
+ leaf_boundaries_odd(29)
20
+ when 40
21
+ leaf_boundaries_even(40)
22
+ when 41
23
+ leaf_boundaries_odd(41)
24
+ when 60
25
+ leaf_boundaries_varian_60
26
+ when 80
27
+ leaf_boundaries_even(80)
28
+ else
29
+ raise ArgumentError, "Unsupported number of leaves: #{nr_leaves}"
30
+ end
31
+ end
32
+
33
+ # Gives an array of MLC leaf position boundaries for ordinary even numbered
34
+ # multi leaf collimators.
35
+ #
36
+ # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
37
+ # @return [Array<Fixnum>] the leaf boundary positions
38
+ #
39
+ def leaf_boundaries_even(nr_leaves)
40
+ Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
41
+ end
42
+
43
+ # Gives an array of MLC leaf position boundaries for ordinary odd numbered
44
+ # multi leaf collimators.
45
+ #
46
+ # @param [Fixnum] nr_leaves the number of leaves (in one leaf bank)
47
+ # @return [Array<Fixnum>] the leaf boundary positions
48
+ #
49
+ def leaf_boundaries_odd(nr_leaves)
50
+ Array.new(nr_leaves-1) {|i| (10 * (i - (0.5 * nr_leaves - 1))).to_i}.unshift(-200).push(200)
51
+ end
52
+
53
+ # Gives an array of MLC leaf position boundaries for the Varian 120 leaf MLC (60 leaves on each bank).
54
+ #
55
+ # @return [Array<Fixnum>] the leaf boundary positions
56
+ #
57
+ def leaf_boundaries_varian_60
58
+ Array.new(10) {|i| (i * 10 - 200).to_i}
59
+ .concat(Array.new(41) {|i| (i * 5 - 100).to_i})
60
+ .concat(Array.new(10) {|i| (i * 10 + 110).to_i})
61
+ end
62
+
63
+ # Computes the CRC checksum of the given line and verifies that
64
+ # this value corresponds with the checksum given at the end of the line.
65
+ #
66
+ # @param [String] line a single line string from an RTPConnect ascii file
67
+ # @param [Hash] options the options to use for verifying the RTP record
68
+ # @option options [Boolean] :ignore_crc if true, the verification method will return true even if the checksum is invalid
69
+ # @return [Boolean] true
70
+ # @raise [ArgumentError] if an invalid line/record is given or the string contains an invalid checksum
71
+ #
72
+ def verify(line, options={})
73
+ last_comma_pos = line.rindex(',')
74
+ raise ArgumentError, "Invalid line encountered; No comma present in the string: #{line}" unless last_comma_pos
75
+ string_to_check = line[0..last_comma_pos]
76
+ string_remaining = line[(last_comma_pos+1)..-1]
77
+ raise ArgumentError, "Invalid line encountered; Valid checksum missing at end of string: #{string_remaining}" unless string_remaining.length >= 3
78
+ checksum_extracted = string_remaining.value.to_i
79
+ checksum_computed = string_to_check.checksum
80
+ raise ArgumentError, "Invalid line encountered: Specified checksum #{checksum_extracted} deviates from the computed checksum #{checksum_computed}." if checksum_extracted != checksum_computed && !options[:ignore_crc]
81
+ return true
82
+ end
83
+
84
+ end
85
+
78
86
  end
@@ -1,625 +1,646 @@
1
- # Copyright 2011-2015 Christoffer Lervag
2
- #
3
- # This program is free software: you can redistribute it and/or modify
4
- # it under the terms of the GNU General Public License as published by
5
- # the Free Software Foundation, either version 3 of the License, or
6
- # (at your option) any later version.
7
- #
8
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
12
- #
13
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
- #
16
- module RTP
17
-
18
- # The Plan class is the highest level Record in the RTPConnect records hierarchy,
19
- # and the one the user will interact with to read, modify and write files.
20
- #
21
- # @note Relations:
22
- # * Parent: nil
23
- # * Children: Prescription, DoseTracking
24
- #
25
- class Plan < Record
26
- include Logging
27
-
28
- # The Record which this instance belongs to (nil by definition).
29
- attr_reader :parent
30
- # An array of Prescription records (if any) that belongs to this Plan.
31
- attr_reader :prescriptions
32
- # The ExtendedPlan record (if any) that belongs to this Plan.
33
- attr_reader :extended_plan
34
- # An array of DoseTracking records (if any) that belongs to this Plan.
35
- attr_reader :dose_trackings
36
- attr_reader :patient_id
37
- attr_reader :patient_last_name
38
- attr_reader :patient_first_name
39
- attr_reader :patient_middle_initial
40
- attr_reader :plan_id
41
- attr_reader :plan_date
42
- attr_reader :plan_time
43
- attr_reader :course_id
44
- attr_reader :diagnosis
45
- attr_reader :md_last_name
46
- attr_reader :md_first_name
47
- attr_reader :md_middle_initial
48
- attr_reader :md_approve_last_name
49
- attr_reader :md_approve_first_name
50
- attr_reader :md_approve_middle_initial
51
- attr_reader :phy_approve_last_name
52
- attr_reader :phy_approve_first_name
53
- attr_reader :phy_approve_middle_initial
54
- attr_reader :author_last_name
55
- attr_reader :author_first_name
56
- attr_reader :author_middle_initial
57
- attr_reader :rtp_mfg
58
- attr_reader :rtp_model
59
- attr_reader :rtp_version
60
- attr_reader :rtp_if_protocol
61
- attr_reader :rtp_if_version
62
-
63
- # Creates a new Plan by loading a plan definition string (i.e. a single line).
64
- #
65
- # @note This method does not perform crc verification on the given string.
66
- # If such verification is desired, use methods ::parse or ::read instead.
67
- # @param [#to_s] string the plan definition record string line
68
- # @param [Hash] options the options to use for loading the plan definition string
69
- # @option options [Boolean] :repair if true, a record containing invalid CSV will be attempted fixed and loaded
70
- # @return [Plan] the created Plan instance
71
- # @raise [ArgumentError] if given a string containing an invalid number of elements
72
- #
73
- def self.load(string, options={})
74
- rtp = self.new
75
- rtp.load(string, options)
76
- end
77
-
78
- # Creates a Plan instance by parsing an RTPConnect string.
79
- #
80
- # @param [#to_s] string an RTPConnect ascii string (with single or multiple lines/records)
81
- # @param [Hash] options the options to use for parsing the RTP string
82
- # @option options [Boolean] :ignore_crc if true, the RTP records will be successfully loaded even if their checksums are invalid
83
- # @option options [Boolean] :repair if true, any RTP records containing invalid CSV will be attempted fixed and loaded
84
- # @option options [Boolean] :skip_unknown if true, unknown records will be skipped, and record instances will be built from the remaining recognized string records
85
- # @return [Plan] the created Plan instance
86
- # @raise [ArgumentError] if given an invalid string record
87
- #
88
- def self.parse(string, options={})
89
- lines = string.to_s.split("\r\n")
90
- # Create the Plan object:
91
- line = lines.first
92
- RTP::verify(line, options)
93
- rtp = self.load(line, options)
94
- lines[1..-1].each do |line|
95
- # Validate, determine type, and process the line accordingly to
96
- # build the hierarchy of records:
97
- RTP::verify(line, options)
98
- values = line.values(options[:repair])
99
- keyword = values.first
100
- method = RTP::PARSE_METHOD[keyword]
101
- if method
102
- rtp.send(method, line)
103
- else
104
- if options[:skip_unknown]
105
- logger.warn("Skipped unknown record definition: #{keyword}")
106
- else
107
- raise ArgumentError, "Unknown keyword #{keyword} extracted from string."
108
- end
109
- end
110
- end
111
- return rtp
112
- end
113
-
114
- # Creates an Plan instance by reading and parsing an RTPConnect file.
115
- #
116
- # @param [String] file a string which specifies the path of the RTPConnect file to be loaded
117
- # @param [Hash] options the options to use for reading the RTP file
118
- # @option options [Boolean] :ignore_crc if true, the RTP records will be successfully loaded even if their checksums are invalid
119
- # @option options [Boolean] :repair if true, any RTP records containing invalid CSV will be attempted fixed and loaded
120
- # @option options [Boolean] :skip_unknown if true, unknown records will be skipped, and record instances will be built from the remaining recognized string records
121
- # @return [Plan] the created Plan instance
122
- # @raise [ArgumentError] if given an invalid file or the file given contains an invalid record
123
- #
124
- def self.read(file, options={})
125
- raise ArgumentError, "Invalid argument 'file'. Expected String, got #{file.class}." unless file.is_a?(String)
126
- # Read the file content:
127
- str = nil
128
- unless File.exist?(file)
129
- logger.error("Invalid (non-existing) file: #{file}")
130
- else
131
- unless File.readable?(file)
132
- logger.error("File exists but I don't have permission to read it: #{file}")
133
- else
134
- if File.directory?(file)
135
- logger.error("Expected a file, got a directory: #{file}")
136
- else
137
- if File.size(file) < 10
138
- logger.error("This file is too small to contain valid RTP information: #{file}.")
139
- else
140
- str = File.open(file, 'rb:ISO8859-1') { |f| f.read }
141
- end
142
- end
143
- end
144
- end
145
- # Parse the file contents and create the RTP::Connect object:
146
- if str
147
- rtp = self.parse(str, options)
148
- else
149
- raise "An RTP::Plan object could not be created from the specified file. Check the log for more details."
150
- end
151
- return rtp
152
- end
153
-
154
- # Creates a new Plan.
155
- #
156
- def initialize
157
- super('PLAN_DEF', 10, 28)
158
- @current_parent = self
159
- # Child records:
160
- @extended_plan = nil
161
- @prescriptions = Array.new
162
- @dose_trackings = Array.new
163
- # No parent (by definition) for the Plan record:
164
- @parent = nil
165
- @attributes = [
166
- # Required:
167
- :keyword,
168
- :patient_id,
169
- :patient_last_name,
170
- :patient_first_name,
171
- :patient_middle_initial,
172
- :plan_id,
173
- :plan_date,
174
- :plan_time,
175
- :course_id,
176
- # Optional:
177
- :diagnosis,
178
- :md_last_name,
179
- :md_first_name,
180
- :md_middle_initial,
181
- :md_approve_last_name,
182
- :md_approve_first_name,
183
- :md_approve_middle_initial,
184
- :phy_approve_last_name,
185
- :phy_approve_first_name,
186
- :phy_approve_middle_initial,
187
- :author_last_name,
188
- :author_first_name,
189
- :author_middle_initial,
190
- :rtp_mfg,
191
- :rtp_model,
192
- :rtp_version,
193
- :rtp_if_protocol,
194
- :rtp_if_version
195
- ]
196
- end
197
-
198
- # Checks for equality.
199
- #
200
- # Other and self are considered equivalent if they are
201
- # of compatible types and their attributes are equivalent.
202
- #
203
- # @param other an object to be compared with self.
204
- # @return [Boolean] true if self and other are considered equivalent
205
- #
206
- def ==(other)
207
- if other.respond_to?(:to_plan)
208
- other.send(:state) == state
209
- end
210
- end
211
-
212
- alias_method :eql?, :==
213
-
214
- # Adds a dose tracking record to this instance.
215
- #
216
- # @param [DoseTracking] child a DoseTracking instance which is to be associated with self
217
- #
218
- def add_dose_tracking(child)
219
- @dose_trackings << child.to_dose_tracking
220
- end
221
-
222
- # Adds an extended plan record to this instance.
223
- #
224
- # @param [ExtendedPlan] child an ExtendedPlan instance which is to be associated with self
225
- #
226
- def add_extended_plan(child)
227
- @extended_plan = child.to_extended_plan
228
- end
229
-
230
- # Adds a prescription site record to this instance.
231
- #
232
- # @param [Prescription] child a Prescription instance which is to be associated with self
233
- #
234
- def add_prescription(child)
235
- @prescriptions << child.to_prescription
236
- end
237
-
238
- # Collects the child records of this instance in a properly sorted array.
239
- #
240
- # @return [Array<Prescription, DoseTracking>] a sorted array of self's child records
241
- #
242
- def children
243
- return [@extended_plan, @prescriptions, @dose_trackings].flatten.compact
244
- end
245
-
246
- # Computes a hash code for this object.
247
- #
248
- # @note Two objects with the same attributes will have the same hash code.
249
- #
250
- # @return [Fixnum] the object's hash code
251
- #
252
- def hash
253
- state.hash
254
- end
255
-
256
- # Returns self.
257
- #
258
- # @return [Plan] self
259
- #
260
- def to_plan
261
- self
262
- end
263
-
264
- # Returns self.
265
- #
266
- # @return [Plan] self
267
- #
268
- def to_rtp
269
- self
270
- end
271
-
272
- # Encodes the Plan object + any hiearchy of child objects,
273
- # to a properly formatted RTPConnect ascii string.
274
- #
275
- # @param [Hash] options an optional hash parameter
276
- # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
277
- # @return [String] an RTP string with a single or multiple lines/records
278
- #
279
- def to_s(options={})
280
- str = encode(options)
281
- children.each do |child|
282
- str += child.to_s(options) unless child.class == ExtendedPlan && options[:version].to_f < 2.5
283
- end
284
- return str
285
- end
286
-
287
- alias :to_str :to_s
288
-
289
- # Writes the Plan object, along with its hiearchy of child objects,
290
- # to a properly formatted RTPConnect ascii file.
291
- #
292
- # @param [String] file a path/file string
293
- # @param [Hash] options an optional hash parameter
294
- # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
295
- #
296
- def write(file, options={})
297
- f = open_file(file)
298
- f.write(to_s(options))
299
- f.close
300
- end
301
-
302
- # Sets the patient_id attribute.
303
- #
304
- # @param [nil, #to_s] value the new attribute value
305
- #
306
- def patient_id=(value)
307
- @patient_id = value && value.to_s
308
- end
309
-
310
- # Sets the patient_last_name attribute.
311
- #
312
- def patient_last_name=(value)
313
- @patient_last_name = value && value.to_s
314
- end
315
-
316
- # Sets the patient_first_name attribute.
317
- #
318
- # @param [nil, #to_s] value the new attribute value
319
- #
320
- def patient_first_name=(value)
321
- @patient_first_name = value && value.to_s
322
- end
323
-
324
- # Sets the patient_middle_initial attribute.
325
- #
326
- # @param [nil, #to_s] value the new attribute value
327
- #
328
- def patient_middle_initial=(value)
329
- @patient_middle_initial = value && value.to_s
330
- end
331
-
332
- # Sets the plan_id attribute.
333
- #
334
- # @param [nil, #to_s] value the new attribute value
335
- #
336
- def plan_id=(value)
337
- @plan_id = value && value.to_s
338
- end
339
-
340
- # Sets the plan_date attribute.
341
- #
342
- # @param [nil, #to_s] value the new attribute value
343
- #
344
- def plan_date=(value)
345
- @plan_date = value && value.to_s
346
- end
347
-
348
- # Sets the plan_time attribute.
349
- #
350
- # @param [nil, #to_s] value the new attribute value
351
- #
352
- def plan_time=(value)
353
- @plan_time = value && value.to_s
354
- end
355
-
356
- # Sets the course_id attribute.
357
- #
358
- # @param [nil, #to_s] value the new attribute value
359
- #
360
- def course_id=(value)
361
- @course_id = value && value.to_s
362
- end
363
-
364
- # Sets the diagnosis attribute.
365
- #
366
- # @param [nil, #to_s] value the new attribute value
367
- #
368
- def diagnosis=(value)
369
- @diagnosis = value && value.to_s
370
- end
371
-
372
- # Sets the md_last_name attribute.
373
- #
374
- # @param [nil, #to_s] value the new attribute value
375
- #
376
- def md_last_name=(value)
377
- @md_last_name = value && value.to_s
378
- end
379
-
380
- # Sets the md_first_name attribute.
381
- #
382
- # @param [nil, #to_s] value the new attribute value
383
- #
384
- def md_first_name=(value)
385
- @md_first_name = value && value.to_s
386
- end
387
-
388
- # Sets the md_middle_initial attribute.
389
- #
390
- # @param [nil, #to_s] value the new attribute value
391
- #
392
- def md_middle_initial=(value)
393
- @md_middle_initial = value && value.to_s
394
- end
395
-
396
- # Sets the md_approve_last_name attribute.
397
- #
398
- # @param [nil, #to_s] value the new attribute value
399
- #
400
- def md_approve_last_name=(value)
401
- @md_approve_last_name = value && value.to_s
402
- end
403
-
404
- # Sets the md_approve_first_name attribute.
405
- #
406
- # @param [nil, #to_s] value the new attribute value
407
- #
408
- def md_approve_first_name=(value)
409
- @md_approve_first_name = value && value.to_s
410
- end
411
-
412
- # Sets the md_approve_middle_initial attribute.
413
- #
414
- # @param [nil, #to_s] value the new attribute value
415
- #
416
- def md_approve_middle_initial=(value)
417
- @md_approve_middle_initial = value && value.to_s
418
- end
419
-
420
- # Sets the phy_approve_last_name attribute.
421
- #
422
- # @param [nil, #to_s] value the new attribute value
423
- #
424
- def phy_approve_last_name=(value)
425
- @phy_approve_last_name = value && value.to_s
426
- end
427
-
428
- # Sets the phy_approve_first_name attribute.
429
- #
430
- # @param [nil, #to_s] value the new attribute value
431
- #
432
- def phy_approve_first_name=(value)
433
- @phy_approve_first_name = value && value.to_s
434
- end
435
-
436
- # Sets the phy_approve_middle_initial attribute.
437
- #
438
- # @param [nil, #to_s] value the new attribute value
439
- #
440
- def phy_approve_middle_initial=(value)
441
- @phy_approve_middle_initial = value && value.to_s
442
- end
443
-
444
- # Sets the author_last_name attribute.
445
- #
446
- # @param [nil, #to_s] value the new attribute value
447
- #
448
- def author_last_name=(value)
449
- @author_last_name = value && value.to_s
450
- end
451
-
452
- # Sets the author_first_name attribute.
453
- #
454
- # @param [nil, #to_s] value the new attribute value
455
- #
456
- def author_first_name=(value)
457
- @author_first_name = value && value.to_s
458
- end
459
-
460
- # Sets the author_middle_initial attribute.
461
- #
462
- # @param [nil, #to_s] value the new attribute value
463
- #
464
- def author_middle_initial=(value)
465
- @author_middle_initial = value && value.to_s
466
- end
467
-
468
- # Sets the rtp_mfg attribute.
469
- #
470
- # @param [nil, #to_s] value the new attribute value
471
- #
472
- def rtp_mfg=(value)
473
- @rtp_mfg = value && value.to_s
474
- end
475
-
476
- # Sets the rtp_model attribute.
477
- #
478
- # @param [nil, #to_s] value the new attribute value
479
- #
480
- def rtp_model=(value)
481
- @rtp_model = value && value.to_s
482
- end
483
-
484
- # Sets the rtp_version attribute.
485
- #
486
- # @param [nil, #to_s] value the new attribute value
487
- #
488
- def rtp_version=(value)
489
- @rtp_version = value && value.to_s
490
- end
491
-
492
- # Sets the rtp_if_protocol attribute.
493
- #
494
- # @param [nil, #to_s] value the new attribute value
495
- #
496
- def rtp_if_protocol=(value)
497
- @rtp_if_protocol = value && value.to_s
498
- end
499
-
500
- # Sets the rtp_if_version attribute.
501
- #
502
- # @param [nil, #to_s] value the new attribute value
503
- #
504
- def rtp_if_version=(value)
505
- @rtp_if_version = value && value.to_s
506
- end
507
-
508
-
509
- private
510
-
511
-
512
- # Creates a control point record from the given string.
513
- #
514
- # @param [String] string a string line containing a control point definition
515
- #
516
- def control_point(string)
517
- cp = ControlPoint.load(string, @current_parent)
518
- @current_parent = cp
519
- end
520
-
521
- # Creates a dose tracking record from the given string.
522
- #
523
- # @param [String] string a string line containing a dose tracking definition
524
- #
525
- def dose_tracking(string)
526
- dt = DoseTracking.load(string, @current_parent)
527
- @current_parent = dt
528
- end
529
-
530
- # Creates an extended plan record from the given string.
531
- #
532
- # @param [String] string a string line containing an extended plan definition
533
- #
534
- def extended_plan_def(string)
535
- ep = ExtendedPlan.load(string, @current_parent)
536
- @current_parent = ep
537
- end
538
-
539
- # Creates an extended treatment field record from the given string.
540
- #
541
- # @param [String] string a string line containing an extended treatment field definition
542
- #
543
- def extended_treatment_field(string)
544
- ef = ExtendedField.load(string, @current_parent)
545
- @current_parent = ef
546
- end
547
-
548
- # Tests if the path/file is writable, creates any folders if necessary, and opens the file for writing.
549
- #
550
- # @param [String] file a path/file string
551
- # @raise if the given file cannot be created
552
- #
553
- def open_file(file)
554
- # Check if file already exists:
555
- if File.exist?(file)
556
- # Is (the existing file) writable?
557
- unless File.writable?(file)
558
- raise "The program does not have permission or resources to create this file: #{file}"
559
- end
560
- else
561
- # File does not exist.
562
- # Check if this file's path contains a folder that does not exist, and therefore needs to be created:
563
- folders = file.split(File::SEPARATOR)
564
- if folders.length > 1
565
- # Remove last element (which should be the file string):
566
- folders.pop
567
- path = folders.join(File::SEPARATOR)
568
- # Check if this path exists:
569
- unless File.directory?(path)
570
- # We need to create (parts of) this path:
571
- require 'fileutils'
572
- FileUtils.mkdir_p(path)
573
- end
574
- end
575
- end
576
- # It has been verified that the file can be created:
577
- return File.new(file, 'wb:ISO8859-1')
578
- end
579
-
580
- # Creates a prescription site record from the given string.
581
- #
582
- # @param [String] string a string line containing a prescription site definition
583
- #
584
- def prescription_site(string)
585
- p = Prescription.load(string, @current_parent)
586
- @current_parent = p
587
- end
588
-
589
- # Creates a site setup record from the given string.
590
- #
591
- # @param [String] string a string line containing a site setup definition
592
- #
593
- def site_setup(string)
594
- s = SiteSetup.load(string, @current_parent)
595
- @current_parent = s
596
- end
597
-
598
- # Collects the attributes of this instance.
599
- #
600
- # @note The CRC is not considered part of the attributes of interest and is excluded
601
- # @return [Array<String>] an array of attributes
602
- #
603
- alias_method :state, :values
604
-
605
- # Creates a treatment field record from the given string.
606
- #
607
- # @param [String] string a string line containing a treatment field definition
608
- #
609
- def treatment_field(string)
610
- f = Field.load(string, @current_parent)
611
- @current_parent = f
612
- end
613
-
614
- # Creates a simulation field record from the given string.
615
- #
616
- # @param [String] string a string line containing a simulation field definition
617
- #
618
- def simulation_field(string)
619
- sf = SimulationField.load(string, @current_parent)
620
- @current_parent = sf
621
- end
622
-
623
- end
624
-
1
+ # Copyright 2011-2016 Christoffer Lervag
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ #
16
+ module RTP
17
+
18
+ # The Plan class is the highest level Record in the RTPConnect records hierarchy,
19
+ # and the one the user will interact with to read, modify and write files.
20
+ #
21
+ # @note Relations:
22
+ # * Parent: nil
23
+ # * Children: Prescription, DoseTracking
24
+ #
25
+ class Plan < Record
26
+ include Logging
27
+
28
+ # The Record which this instance belongs to (nil by definition).
29
+ attr_reader :parent
30
+ # An array of Prescription records (if any) that belongs to this Plan.
31
+ attr_reader :prescriptions
32
+ # The ExtendedPlan record (if any) that belongs to this Plan.
33
+ attr_reader :extended_plan
34
+ # An array of DoseTracking records (if any) that belongs to this Plan.
35
+ attr_reader :dose_trackings
36
+ attr_reader :patient_id
37
+ attr_reader :patient_last_name
38
+ attr_reader :patient_first_name
39
+ attr_reader :patient_middle_initial
40
+ attr_reader :plan_id
41
+ attr_reader :plan_date
42
+ attr_reader :plan_time
43
+ attr_reader :course_id
44
+ attr_reader :diagnosis
45
+ attr_reader :md_last_name
46
+ attr_reader :md_first_name
47
+ attr_reader :md_middle_initial
48
+ attr_reader :md_approve_last_name
49
+ attr_reader :md_approve_first_name
50
+ attr_reader :md_approve_middle_initial
51
+ attr_reader :phy_approve_last_name
52
+ attr_reader :phy_approve_first_name
53
+ attr_reader :phy_approve_middle_initial
54
+ attr_reader :author_last_name
55
+ attr_reader :author_first_name
56
+ attr_reader :author_middle_initial
57
+ attr_reader :rtp_mfg
58
+ attr_reader :rtp_model
59
+ attr_reader :rtp_version
60
+ attr_reader :rtp_if_protocol
61
+ attr_reader :rtp_if_version
62
+
63
+ # Creates a new Plan by loading a plan definition string (i.e. a single line).
64
+ #
65
+ # @note This method does not perform crc verification on the given string.
66
+ # If such verification is desired, use methods ::parse or ::read instead.
67
+ # @param [#to_s] string the plan definition record string line
68
+ # @param [Hash] options the options to use for loading the plan definition string
69
+ # @option options [Boolean] :repair if true, a record containing invalid CSV will be attempted fixed and loaded
70
+ # @return [Plan] the created Plan instance
71
+ # @raise [ArgumentError] if given a string containing an invalid number of elements
72
+ #
73
+ def self.load(string, options={})
74
+ rtp = self.new
75
+ rtp.load(string, options)
76
+ end
77
+
78
+ # Creates a Plan instance by parsing an RTPConnect string.
79
+ #
80
+ # @param [#to_s] string an RTPConnect ascii string (with single or multiple lines/records)
81
+ # @param [Hash] options the options to use for parsing the RTP string
82
+ # @option options [Boolean] :ignore_crc if true, the RTP records will be successfully loaded even if their checksums are invalid
83
+ # @option options [Boolean] :repair if true, any RTP records containing invalid CSV will be attempted fixed and loaded
84
+ # @option options [Boolean] :skip_unknown if true, unknown records will be skipped, and record instances will be built from the remaining recognized string records
85
+ # @return [Plan] the created Plan instance
86
+ # @raise [ArgumentError] if given an invalid string record
87
+ #
88
+ def self.parse(string, options={})
89
+ lines = string.to_s.split("\r\n")
90
+ # Create the Plan object:
91
+ line = lines.first
92
+ RTP.verify(line, options)
93
+ rtp = self.load(line, options)
94
+ lines[1..-1].each do |line|
95
+ # Validate, determine type, and process the line accordingly to
96
+ # build the hierarchy of records:
97
+ RTP.verify(line, options)
98
+ values = line.values(options[:repair])
99
+ keyword = values.first
100
+ method = RTP::PARSE_METHOD[keyword]
101
+ if method
102
+ rtp.send(method, line)
103
+ else
104
+ if options[:skip_unknown]
105
+ logger.warn("Skipped unknown record definition: #{keyword}")
106
+ else
107
+ raise ArgumentError, "Unknown keyword #{keyword} extracted from string."
108
+ end
109
+ end
110
+ end
111
+ return rtp
112
+ end
113
+
114
+ # Creates an Plan instance by reading and parsing an RTPConnect file.
115
+ #
116
+ # @param [String] file a string which specifies the path of the RTPConnect file to be loaded
117
+ # @param [Hash] options the options to use for reading the RTP file
118
+ # @option options [Boolean] :ignore_crc if true, the RTP records will be successfully loaded even if their checksums are invalid
119
+ # @option options [Boolean] :repair if true, any RTP records containing invalid CSV will be attempted fixed and loaded
120
+ # @option options [Boolean] :skip_unknown if true, unknown records will be skipped, and record instances will be built from the remaining recognized string records
121
+ # @return [Plan] the created Plan instance
122
+ # @raise [ArgumentError] if given an invalid file or the file given contains an invalid record
123
+ #
124
+ def self.read(file, options={})
125
+ raise ArgumentError, "Invalid argument 'file'. Expected String, got #{file.class}." unless file.is_a?(String)
126
+ # Read the file content:
127
+ str = nil
128
+ unless File.exist?(file)
129
+ logger.error("Invalid (non-existing) file: #{file}")
130
+ else
131
+ unless File.readable?(file)
132
+ logger.error("File exists but I don't have permission to read it: #{file}")
133
+ else
134
+ if File.directory?(file)
135
+ logger.error("Expected a file, got a directory: #{file}")
136
+ else
137
+ if File.size(file) < 10
138
+ logger.error("This file is too small to contain valid RTP information: #{file}.")
139
+ else
140
+ str = File.open(file, 'rb:ISO8859-1') { |f| f.read }
141
+ end
142
+ end
143
+ end
144
+ end
145
+ # Parse the file contents and create the RTP instance:
146
+ if str
147
+ rtp = self.parse(str, options)
148
+ else
149
+ raise "An RTP::Plan object could not be created from the specified file. Check the log for more details."
150
+ end
151
+ return rtp
152
+ end
153
+
154
+ # Creates a new Plan.
155
+ #
156
+ def initialize
157
+ super('PLAN_DEF', 10, 28)
158
+ @current_parent = self
159
+ # Child records:
160
+ @extended_plan = nil
161
+ @prescriptions = Array.new
162
+ @dose_trackings = Array.new
163
+ # No parent (by definition) for the Plan record:
164
+ @parent = nil
165
+ @attributes = [
166
+ # Required:
167
+ :keyword,
168
+ :patient_id,
169
+ :patient_last_name,
170
+ :patient_first_name,
171
+ :patient_middle_initial,
172
+ :plan_id,
173
+ :plan_date,
174
+ :plan_time,
175
+ :course_id,
176
+ # Optional:
177
+ :diagnosis,
178
+ :md_last_name,
179
+ :md_first_name,
180
+ :md_middle_initial,
181
+ :md_approve_last_name,
182
+ :md_approve_first_name,
183
+ :md_approve_middle_initial,
184
+ :phy_approve_last_name,
185
+ :phy_approve_first_name,
186
+ :phy_approve_middle_initial,
187
+ :author_last_name,
188
+ :author_first_name,
189
+ :author_middle_initial,
190
+ :rtp_mfg,
191
+ :rtp_model,
192
+ :rtp_version,
193
+ :rtp_if_protocol,
194
+ :rtp_if_version
195
+ ]
196
+ end
197
+
198
+ # Checks for equality.
199
+ #
200
+ # Other and self are considered equivalent if they are
201
+ # of compatible types and their attributes are equivalent.
202
+ #
203
+ # @param other an object to be compared with self.
204
+ # @return [Boolean] true if self and other are considered equivalent
205
+ #
206
+ def ==(other)
207
+ if other.respond_to?(:to_plan)
208
+ other.send(:state) == state
209
+ end
210
+ end
211
+
212
+ alias_method :eql?, :==
213
+
214
+ # Adds a dose tracking record to this instance.
215
+ #
216
+ # @param [DoseTracking] child a DoseTracking instance which is to be associated with self
217
+ #
218
+ def add_dose_tracking(child)
219
+ @dose_trackings << child.to_dose_tracking
220
+ child.parent = self
221
+ end
222
+
223
+ # Adds an extended plan record to this instance.
224
+ #
225
+ # @param [ExtendedPlan] child an ExtendedPlan instance which is to be associated with self
226
+ #
227
+ def add_extended_plan(child)
228
+ @extended_plan = child.to_extended_plan
229
+ child.parent = self
230
+ end
231
+
232
+ # Adds a prescription site record to this instance.
233
+ #
234
+ # @param [Prescription] child a Prescription instance which is to be associated with self
235
+ #
236
+ def add_prescription(child)
237
+ @prescriptions << child.to_prescription
238
+ child.parent = self
239
+ end
240
+
241
+ # Collects the child records of this instance in a properly sorted array.
242
+ #
243
+ # @return [Array<Prescription, DoseTracking>] a sorted array of self's child records
244
+ #
245
+ def children
246
+ return [@extended_plan, @prescriptions, @dose_trackings].flatten.compact
247
+ end
248
+
249
+ # Removes the reference of the given instance from this instance.
250
+ #
251
+ # @param [ExtendedPlan, Prescription, DoseTracking] record a child record to be removed from this instance
252
+ #
253
+ def delete(record)
254
+ case record
255
+ when Prescription
256
+ delete_child(:prescriptions, record)
257
+ when DoseTracking
258
+ delete_child(:dose_trackings, record)
259
+ when ExtendedPlan
260
+ delete_extended_plan
261
+ else
262
+ logger.warn("Unknown class (record) given to Plan#delete: #{record.class}")
263
+ end
264
+ end
265
+
266
+ # Removes all dose_tracking references from this instance.
267
+ #
268
+ def delete_dose_trackings
269
+ delete_children(:dose_trackings)
270
+ end
271
+
272
+ # Removes the extended plan reference from this instance.
273
+ #
274
+ def delete_extended_plan
275
+ delete_child(:extended_plan)
276
+ end
277
+
278
+ # Removes all prescription references from this instance.
279
+ #
280
+ def delete_prescriptions
281
+ delete_children(:prescriptions)
282
+ end
283
+
284
+ # Computes a hash code for this object.
285
+ #
286
+ # @note Two objects with the same attributes will have the same hash code.
287
+ #
288
+ # @return [Fixnum] the object's hash code
289
+ #
290
+ def hash
291
+ state.hash
292
+ end
293
+
294
+ # Returns self.
295
+ #
296
+ # @return [Plan] self
297
+ #
298
+ def to_plan
299
+ self
300
+ end
301
+
302
+ # Returns self.
303
+ #
304
+ # @return [Plan] self
305
+ #
306
+ def to_rtp
307
+ self
308
+ end
309
+
310
+ # Writes the Plan object, along with its hiearchy of child objects,
311
+ # to a properly formatted RTPConnect ascii file.
312
+ #
313
+ # @param [String] file a path/file string
314
+ # @param [Hash] options an optional hash parameter
315
+ # @option options [Float] :version the Mosaiq compatibility version number (e.g. 2.4) used for the output
316
+ #
317
+ def write(file, options={})
318
+ f = open_file(file)
319
+ f.write(to_s(options))
320
+ f.close
321
+ end
322
+
323
+ # Sets the patient_id attribute.
324
+ #
325
+ # @param [nil, #to_s] value the new attribute value
326
+ #
327
+ def patient_id=(value)
328
+ @patient_id = value && value.to_s
329
+ end
330
+
331
+ # Sets the patient_last_name attribute.
332
+ #
333
+ def patient_last_name=(value)
334
+ @patient_last_name = value && value.to_s
335
+ end
336
+
337
+ # Sets the patient_first_name attribute.
338
+ #
339
+ # @param [nil, #to_s] value the new attribute value
340
+ #
341
+ def patient_first_name=(value)
342
+ @patient_first_name = value && value.to_s
343
+ end
344
+
345
+ # Sets the patient_middle_initial attribute.
346
+ #
347
+ # @param [nil, #to_s] value the new attribute value
348
+ #
349
+ def patient_middle_initial=(value)
350
+ @patient_middle_initial = value && value.to_s
351
+ end
352
+
353
+ # Sets the plan_id attribute.
354
+ #
355
+ # @param [nil, #to_s] value the new attribute value
356
+ #
357
+ def plan_id=(value)
358
+ @plan_id = value && value.to_s
359
+ end
360
+
361
+ # Sets the plan_date attribute.
362
+ #
363
+ # @param [nil, #to_s] value the new attribute value
364
+ #
365
+ def plan_date=(value)
366
+ @plan_date = value && value.to_s
367
+ end
368
+
369
+ # Sets the plan_time attribute.
370
+ #
371
+ # @param [nil, #to_s] value the new attribute value
372
+ #
373
+ def plan_time=(value)
374
+ @plan_time = value && value.to_s
375
+ end
376
+
377
+ # Sets the course_id attribute.
378
+ #
379
+ # @param [nil, #to_s] value the new attribute value
380
+ #
381
+ def course_id=(value)
382
+ @course_id = value && value.to_s
383
+ end
384
+
385
+ # Sets the diagnosis attribute.
386
+ #
387
+ # @param [nil, #to_s] value the new attribute value
388
+ #
389
+ def diagnosis=(value)
390
+ @diagnosis = value && value.to_s
391
+ end
392
+
393
+ # Sets the md_last_name attribute.
394
+ #
395
+ # @param [nil, #to_s] value the new attribute value
396
+ #
397
+ def md_last_name=(value)
398
+ @md_last_name = value && value.to_s
399
+ end
400
+
401
+ # Sets the md_first_name attribute.
402
+ #
403
+ # @param [nil, #to_s] value the new attribute value
404
+ #
405
+ def md_first_name=(value)
406
+ @md_first_name = value && value.to_s
407
+ end
408
+
409
+ # Sets the md_middle_initial attribute.
410
+ #
411
+ # @param [nil, #to_s] value the new attribute value
412
+ #
413
+ def md_middle_initial=(value)
414
+ @md_middle_initial = value && value.to_s
415
+ end
416
+
417
+ # Sets the md_approve_last_name attribute.
418
+ #
419
+ # @param [nil, #to_s] value the new attribute value
420
+ #
421
+ def md_approve_last_name=(value)
422
+ @md_approve_last_name = value && value.to_s
423
+ end
424
+
425
+ # Sets the md_approve_first_name attribute.
426
+ #
427
+ # @param [nil, #to_s] value the new attribute value
428
+ #
429
+ def md_approve_first_name=(value)
430
+ @md_approve_first_name = value && value.to_s
431
+ end
432
+
433
+ # Sets the md_approve_middle_initial attribute.
434
+ #
435
+ # @param [nil, #to_s] value the new attribute value
436
+ #
437
+ def md_approve_middle_initial=(value)
438
+ @md_approve_middle_initial = value && value.to_s
439
+ end
440
+
441
+ # Sets the phy_approve_last_name attribute.
442
+ #
443
+ # @param [nil, #to_s] value the new attribute value
444
+ #
445
+ def phy_approve_last_name=(value)
446
+ @phy_approve_last_name = value && value.to_s
447
+ end
448
+
449
+ # Sets the phy_approve_first_name attribute.
450
+ #
451
+ # @param [nil, #to_s] value the new attribute value
452
+ #
453
+ def phy_approve_first_name=(value)
454
+ @phy_approve_first_name = value && value.to_s
455
+ end
456
+
457
+ # Sets the phy_approve_middle_initial attribute.
458
+ #
459
+ # @param [nil, #to_s] value the new attribute value
460
+ #
461
+ def phy_approve_middle_initial=(value)
462
+ @phy_approve_middle_initial = value && value.to_s
463
+ end
464
+
465
+ # Sets the author_last_name attribute.
466
+ #
467
+ # @param [nil, #to_s] value the new attribute value
468
+ #
469
+ def author_last_name=(value)
470
+ @author_last_name = value && value.to_s
471
+ end
472
+
473
+ # Sets the author_first_name attribute.
474
+ #
475
+ # @param [nil, #to_s] value the new attribute value
476
+ #
477
+ def author_first_name=(value)
478
+ @author_first_name = value && value.to_s
479
+ end
480
+
481
+ # Sets the author_middle_initial attribute.
482
+ #
483
+ # @param [nil, #to_s] value the new attribute value
484
+ #
485
+ def author_middle_initial=(value)
486
+ @author_middle_initial = value && value.to_s
487
+ end
488
+
489
+ # Sets the rtp_mfg attribute.
490
+ #
491
+ # @param [nil, #to_s] value the new attribute value
492
+ #
493
+ def rtp_mfg=(value)
494
+ @rtp_mfg = value && value.to_s
495
+ end
496
+
497
+ # Sets the rtp_model attribute.
498
+ #
499
+ # @param [nil, #to_s] value the new attribute value
500
+ #
501
+ def rtp_model=(value)
502
+ @rtp_model = value && value.to_s
503
+ end
504
+
505
+ # Sets the rtp_version attribute.
506
+ #
507
+ # @param [nil, #to_s] value the new attribute value
508
+ #
509
+ def rtp_version=(value)
510
+ @rtp_version = value && value.to_s
511
+ end
512
+
513
+ # Sets the rtp_if_protocol attribute.
514
+ #
515
+ # @param [nil, #to_s] value the new attribute value
516
+ #
517
+ def rtp_if_protocol=(value)
518
+ @rtp_if_protocol = value && value.to_s
519
+ end
520
+
521
+ # Sets the rtp_if_version attribute.
522
+ #
523
+ # @param [nil, #to_s] value the new attribute value
524
+ #
525
+ def rtp_if_version=(value)
526
+ @rtp_if_version = value && value.to_s
527
+ end
528
+
529
+
530
+ private
531
+
532
+
533
+ # Creates a control point record from the given string.
534
+ #
535
+ # @param [String] string a string line containing a control point definition
536
+ #
537
+ def control_point(string)
538
+ cp = ControlPoint.load(string, @current_parent)
539
+ @current_parent = cp
540
+ end
541
+
542
+ # Creates a dose tracking record from the given string.
543
+ #
544
+ # @param [String] string a string line containing a dose tracking definition
545
+ #
546
+ def dose_tracking(string)
547
+ dt = DoseTracking.load(string, @current_parent)
548
+ @current_parent = dt
549
+ end
550
+
551
+ # Creates an extended plan record from the given string.
552
+ #
553
+ # @param [String] string a string line containing an extended plan definition
554
+ #
555
+ def extended_plan_def(string)
556
+ ep = ExtendedPlan.load(string, @current_parent)
557
+ @current_parent = ep
558
+ end
559
+
560
+ # Creates an extended treatment field record from the given string.
561
+ #
562
+ # @param [String] string a string line containing an extended treatment field definition
563
+ #
564
+ def extended_treatment_field(string)
565
+ ef = ExtendedField.load(string, @current_parent)
566
+ @current_parent = ef
567
+ end
568
+
569
+ # Tests if the path/file is writable, creates any folders if necessary, and opens the file for writing.
570
+ #
571
+ # @param [String] file a path/file string
572
+ # @raise if the given file cannot be created
573
+ #
574
+ def open_file(file)
575
+ # Check if file already exists:
576
+ if File.exist?(file)
577
+ # Is (the existing file) writable?
578
+ unless File.writable?(file)
579
+ raise "The program does not have permission or resources to create this file: #{file}"
580
+ end
581
+ else
582
+ # File does not exist.
583
+ # Check if this file's path contains a folder that does not exist, and therefore needs to be created:
584
+ folders = file.split(File::SEPARATOR)
585
+ if folders.length > 1
586
+ # Remove last element (which should be the file string):
587
+ folders.pop
588
+ path = folders.join(File::SEPARATOR)
589
+ # Check if this path exists:
590
+ unless File.directory?(path)
591
+ # We need to create (parts of) this path:
592
+ require 'fileutils'
593
+ FileUtils.mkdir_p(path)
594
+ end
595
+ end
596
+ end
597
+ # It has been verified that the file can be created:
598
+ return File.new(file, 'wb:ISO8859-1')
599
+ end
600
+
601
+ # Creates a prescription site record from the given string.
602
+ #
603
+ # @param [String] string a string line containing a prescription site definition
604
+ #
605
+ def prescription_site(string)
606
+ p = Prescription.load(string, @current_parent)
607
+ @current_parent = p
608
+ end
609
+
610
+ # Creates a site setup record from the given string.
611
+ #
612
+ # @param [String] string a string line containing a site setup definition
613
+ #
614
+ def site_setup(string)
615
+ s = SiteSetup.load(string, @current_parent)
616
+ @current_parent = s
617
+ end
618
+
619
+ # Collects the attributes of this instance.
620
+ #
621
+ # @note The CRC is not considered part of the attributes of interest and is excluded
622
+ # @return [Array<String>] an array of attributes
623
+ #
624
+ alias_method :state, :values
625
+
626
+ # Creates a treatment field record from the given string.
627
+ #
628
+ # @param [String] string a string line containing a treatment field definition
629
+ #
630
+ def treatment_field(string)
631
+ f = Field.load(string, @current_parent)
632
+ @current_parent = f
633
+ end
634
+
635
+ # Creates a simulation field record from the given string.
636
+ #
637
+ # @param [String] string a string line containing a simulation field definition
638
+ #
639
+ def simulation_field(string)
640
+ sf = SimulationField.load(string, @current_parent)
641
+ @current_parent = sf
642
+ end
643
+
644
+ end
645
+
625
646
  end