rtp-connect 1.8 → 1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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