rtp-connect 1.6 → 1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,63 +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
- [-200, -135, -125, -115, -105, -95, -85, -75, -65, -55, -45, -35, -25,
20
- -15, -5, 5, 15, 25, 35, 45, 55, 65, 75, 85, 95, 105, 115, 125, 135, 200
21
- ]
22
- when 40
23
- Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
24
- when 41
25
- [-200, -195, -185, -175, -165, -155, -145, -135, -125, -115,
26
- -105, -95, -85, -75, -65, -55, -45, -35, -25, -15, -5, 5, 15, 25, 35, 45,
27
- 55, 65, 75, 85, 95, 105, 115, 125, 135, 145, 155, 165, 175, 185, 195, 200
28
- ]
29
- when 60
30
- [-200, -190, -180, -170, -160, -150, -140, -130, -120, -110,
31
- -100, -95, -90, -85, -80, -75, -70, -65, -60, -55, -50, -45, -40, -35, -30,
32
- -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65,
33
- 70, 75, 80, 85, 90, 95, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200
34
- ]
35
- when 80
36
- Array.new(nr_leaves+1) {|i| (i * 400 / nr_leaves.to_f - 200).to_i}
37
- else
38
- raise ArgumentError, "Unsupported number of leaves: #{nr_leaves}"
39
- end
40
- end
41
-
42
- # Computes the CRC checksum of the given line and verifies that
43
- # this value corresponds with the checksum given at the end of the line.
44
- #
45
- # @param [String] line a single line string from an RTPConnect ascii file
46
- # @return [Boolean] true
47
- # @raise [ArgumentError] if an invalid line/record is given or the string contains an invalid checksum
48
- #
49
- def verify(line)
50
- last_comma_pos = line.rindex(',')
51
- raise ArgumentError, "Invalid line encountered; No comma present in the string: #{line}" unless last_comma_pos
52
- string_to_check = line[0..last_comma_pos]
53
- string_remaining = line[(last_comma_pos+1)..-1]
54
- raise ArgumentError, "Invalid line encountered; Valid checksum missing at end of string: #{string_remaining}" unless string_remaining.length >= 3
55
- checksum_extracted = string_remaining.value.to_i
56
- checksum_computed = string_to_check.checksum
57
- raise ArgumentError, "Invalid line encountered: Specified checskum #{checksum_extracted} deviates from the computed checksum #{checksum_computed}." if checksum_extracted != checksum_computed
58
- return true
59
- end
60
-
61
- end
62
-
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
+
63
86
  end
@@ -1,637 +1,646 @@
1
- # Copyright 2011-2013 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
- # An array of DoseTracking records (if any) that belongs to this Plan.
33
- attr_reader :dose_trackings
34
- attr_reader :patient_id
35
- attr_reader :patient_last_name
36
- attr_reader :patient_first_name
37
- attr_reader :patient_middle_initial
38
- attr_reader :plan_id
39
- attr_reader :plan_date
40
- attr_reader :plan_time
41
- attr_reader :course_id
42
- attr_reader :diagnosis
43
- attr_reader :md_last_name
44
- attr_reader :md_first_name
45
- attr_reader :md_middle_initial
46
- attr_reader :md_approve_last_name
47
- attr_reader :md_approve_first_name
48
- attr_reader :md_approve_middle_initial
49
- attr_reader :phy_approve_last_name
50
- attr_reader :phy_approve_first_name
51
- attr_reader :phy_approve_middle_initial
52
- attr_reader :author_last_name
53
- attr_reader :author_first_name
54
- attr_reader :author_middle_initial
55
- attr_reader :rtp_mfg
56
- attr_reader :rtp_model
57
- attr_reader :rtp_version
58
- attr_reader :rtp_if_protocol
59
- attr_reader :rtp_if_version
60
-
61
- # Creates a new Plan by loading a plan definition string (i.e. a single line).
62
- #
63
- # @note This method does not perform crc verification on the given string.
64
- # If such verification is desired, use methods ::parse or ::read instead.
65
- # @param [#to_s] string the plan definition record string line
66
- # @return [Plan] the created Plan instance
67
- # @raise [ArgumentError] if given a string containing an invalid number of elements
68
- #
69
- def self.load(string)
70
- # Get the quote-less values:
71
- values = string.to_s.values
72
- low_limit = 10
73
- high_limit = 28
74
- raise ArgumentError, "Invalid argument 'string': Expected at least #{low_limit} elements, got #{values.length}." if values.length < low_limit
75
- RTP.logger.warn "The number of elements (#{values.length}) for this Plan record exceeds the known number of data items for this record (#{high_limit}). This may indicate an invalid record or that the RTP format has recently been expanded with new items." if values.length > high_limit
76
- rtp = self.new
77
- # Assign the values to attributes:
78
- rtp.keyword = values[0]
79
- rtp.patient_id = values[1]
80
- rtp.patient_last_name = values[2]
81
- rtp.patient_first_name = values[3]
82
- rtp.patient_middle_initial = values[4]
83
- rtp.plan_id = values[5]
84
- rtp.plan_date = values[6]
85
- rtp.plan_time = values[7]
86
- rtp.course_id = values[8]
87
- rtp.diagnosis = values[9]
88
- rtp.md_last_name = values[10]
89
- rtp.md_first_name = values[11]
90
- rtp.md_middle_initial = values[12]
91
- rtp.md_approve_last_name = values[13]
92
- rtp.md_approve_first_name = values[14]
93
- rtp.md_approve_middle_initial = values[15]
94
- rtp.phy_approve_last_name = values[16]
95
- rtp.phy_approve_first_name = values[17]
96
- rtp.phy_approve_middle_initial = values[18]
97
- rtp.author_last_name = values[19]
98
- rtp.author_first_name = values[20]
99
- rtp.author_middle_initial = values[21]
100
- rtp.rtp_mfg = values[22]
101
- rtp.rtp_model = values[23]
102
- rtp.rtp_version = values[24]
103
- rtp.rtp_if_protocol = values[25]
104
- rtp.rtp_if_version = values[26]
105
- rtp.crc = values[-1]
106
- return rtp
107
- end
108
-
109
- # Creates a Plan instance by parsing an RTPConnect string.
110
- #
111
- # @param [#to_s] string an RTPConnect ascii string (with single or multiple lines/records)
112
- # @return [Plan] the created Plan instance
113
- # @raise [ArgumentError] if given an invalid string record
114
- #
115
- def self.parse(string)
116
- lines = string.to_s.split("\r\n")
117
- # Create the Plan object:
118
- line = lines.first
119
- RTP::verify(line)
120
- rtp = self.load(line)
121
- lines[1..-1].each do |line|
122
- # Validate, determine type, and process the line accordingly to
123
- # build the hierarchy of records:
124
- RTP::verify(line)
125
- values = line.values
126
- keyword = values.first
127
- method = RTP::PARSE_METHOD[keyword]
128
- raise ArgumentError, "Unknown keyword #{keyword} extracted from string." unless method
129
- rtp.send(method, line)
130
- end
131
- return rtp
132
- end
133
-
134
- # Creates an Plan instance by reading and parsing an RTPConnect file.
135
- #
136
- # @param [String] file a string which specifies the path of the RTPConnect file to be loaded
137
- # @return [Plan] the created Plan instance
138
- # @raise [ArgumentError] if given an invalid file or the file given contains an invalid record
139
- #
140
- def self.read(file)
141
- raise ArgumentError, "Invalid argument 'file'. Expected String, got #{file.class}." unless file.is_a?(String)
142
- # Read the file content:
143
- str = nil
144
- unless File.exist?(file)
145
- logger.error("Invalid (non-existing) file: #{file}")
146
- else
147
- unless File.readable?(file)
148
- logger.error("File exists but I don't have permission to read it: #{file}")
149
- else
150
- if File.directory?(file)
151
- logger.error("Expected a file, got a directory: #{file}")
152
- else
153
- if File.size(file) < 10
154
- logger.error("This file is too small to contain valid RTP information: #{file}.")
155
- else
156
- str = File.open(file, 'rb:ISO8859-1') { |f| f.read }
157
- end
158
- end
159
- end
160
- end
161
- # Parse the file contents and create the RTP::Connect object:
162
- if str
163
- rtp = self.parse(str)
164
- else
165
- raise "An RTP::Plan object could not be created from the specified file. Check the log for more details."
166
- end
167
- return rtp
168
- end
169
-
170
- # Creates a new Plan.
171
- #
172
- def initialize
173
- @current_parent = self
174
- # Child records:
175
- @prescriptions = Array.new
176
- @dose_trackings = Array.new
177
- # No parent (by definition) for the Plan record:
178
- @parent = nil
179
- @keyword = 'PLAN_DEF'
180
- end
181
-
182
- # Checks for equality.
183
- #
184
- # Other and self are considered equivalent if they are
185
- # of compatible types and their attributes are equivalent.
186
- #
187
- # @param other an object to be compared with self.
188
- # @return [Boolean] true if self and other are considered equivalent
189
- #
190
- def ==(other)
191
- if other.respond_to?(:to_plan)
192
- other.send(:state) == state
193
- end
194
- end
195
-
196
- alias_method :eql?, :==
197
-
198
- # Adds a dose tracking record to this instance.
199
- #
200
- # @param [DoseTracking] child a DoseTracking instance which is to be associated with self
201
- #
202
- def add_dose_tracking(child)
203
- @dose_trackings << child.to_dose_tracking
204
- end
205
-
206
- # Adds a prescription site record to this instance.
207
- #
208
- # @param [Prescription] child a Prescription instance which is to be associated with self
209
- #
210
- def add_prescription(child)
211
- @prescriptions << child.to_prescription
212
- end
213
-
214
- # Collects the child records of this instance in a properly sorted array.
215
- #
216
- # @return [Array<Prescription, DoseTracking>] a sorted array of self's child records
217
- #
218
- def children
219
- return [@prescriptions, @dose_trackings].flatten.compact
220
- end
221
-
222
- # Computes a hash code for this object.
223
- #
224
- # @note Two objects with the same attributes will have the same hash code.
225
- #
226
- # @return [Fixnum] the object's hash code
227
- #
228
- def hash
229
- state.hash
230
- end
231
-
232
- # Collects the values (attributes) of this instance.
233
- #
234
- # @note The CRC is not considered part of the actual values and is excluded.
235
- # @return [Array<String>] an array of attributes (in the same order as they appear in the RTP string)
236
- #
237
- def values
238
- return [
239
- @keyword,
240
- @patient_id,
241
- @patient_last_name,
242
- @patient_first_name,
243
- @patient_middle_initial,
244
- @plan_id,
245
- @plan_date,
246
- @plan_time,
247
- @course_id,
248
- @diagnosis,
249
- @md_last_name,
250
- @md_first_name,
251
- @md_middle_initial,
252
- @md_approve_last_name,
253
- @md_approve_first_name,
254
- @md_approve_middle_initial,
255
- @phy_approve_last_name,
256
- @phy_approve_first_name,
257
- @phy_approve_middle_initial,
258
- @author_last_name,
259
- @author_first_name,
260
- @author_middle_initial,
261
- @rtp_mfg,
262
- @rtp_model,
263
- @rtp_version,
264
- @rtp_if_protocol,
265
- @rtp_if_version
266
- ]
267
- end
268
-
269
- # Returns self.
270
- #
271
- # @return [Plan] self
272
- #
273
- def to_plan
274
- self
275
- end
276
-
277
- # Returns self.
278
- #
279
- # @return [Plan] self
280
- #
281
- def to_rtp
282
- self
283
- end
284
-
285
- # Encodes the Plan object + any hiearchy of child objects,
286
- # to a properly formatted RTPConnect ascii string.
287
- #
288
- # @return [String] an RTP string with a single or multiple lines/records
289
- #
290
- def to_s
291
- str = encode #.force_encoding('utf-8')
292
- children.each do |child|
293
- str += child.to_s #.force_encoding('utf-8')
294
- end
295
- return str
296
- end
297
-
298
- alias :to_str :to_s
299
-
300
- # Writes the Plan object, along with its hiearchy of child objects,
301
- # to a properly formatted RTPConnect ascii file.
302
- #
303
- # @param [String] file a path/file string
304
- #
305
- def write(file)
306
- f = open_file(file)
307
- f.write(to_s)
308
- f.close
309
- end
310
-
311
- # Sets the keyword attribute.
312
- #
313
- # @note Since only a specific string is accepted, this is more of an argument check than a traditional setter method
314
- # @param [#to_s] value the new attribute value
315
- # @raise [ArgumentError] if given an unexpected keyword
316
- #
317
- def keyword=(value)
318
- value = value.to_s.upcase
319
- raise ArgumentError, "Invalid keyword. Expected 'PLAN_DEF', got #{value}." unless value == "PLAN_DEF"
320
- @keyword = value
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 treatment field record from the given string.
552
- #
553
- # @param [String] string a string line containing an extended treatment field definition
554
- #
555
- def extended_treatment_field(string)
556
- ef = ExtendedField.load(string, @current_parent)
557
- @current_parent = ef
558
- end
559
-
560
- # Tests if the path/file is writable, creates any folders if necessary, and opens the file for writing.
561
- #
562
- # @param [String] file a path/file string
563
- # @raise if the given file cannot be created
564
- #
565
- def open_file(file)
566
- # Check if file already exists:
567
- if File.exist?(file)
568
- # Is (the existing file) writable?
569
- unless File.writable?(file)
570
- raise "The program does not have permission or resources to create this file: #{file}"
571
- end
572
- else
573
- # File does not exist.
574
- # Check if this file's path contains a folder that does not exist, and therefore needs to be created:
575
- folders = file.split(File::SEPARATOR)
576
- if folders.length > 1
577
- # Remove last element (which should be the file string):
578
- folders.pop
579
- path = folders.join(File::SEPARATOR)
580
- # Check if this path exists:
581
- unless File.directory?(path)
582
- # We need to create (parts of) this path:
583
- require 'fileutils'
584
- FileUtils.mkdir_p(path)
585
- end
586
- end
587
- end
588
- # It has been verified that the file can be created:
589
- return File.new(file, 'wb:ISO8859-1')
590
- end
591
-
592
- # Creates a prescription site record from the given string.
593
- #
594
- # @param [String] string a string line containing a prescription site definition
595
- #
596
- def prescription_site(string)
597
- p = Prescription.load(string, @current_parent)
598
- @current_parent = p
599
- end
600
-
601
- # Creates a site setup record from the given string.
602
- #
603
- # @param [String] string a string line containing a site setup definition
604
- #
605
- def site_setup(string)
606
- s = SiteSetup.load(string, @current_parent)
607
- @current_parent = s
608
- end
609
-
610
- # Collects the attributes of this instance.
611
- #
612
- # @note The CRC is not considered part of the attributes of interest and is excluded
613
- # @return [Array<String>] an array of attributes
614
- #
615
- alias_method :state, :values
616
-
617
- # Creates a treatment field record from the given string.
618
- #
619
- # @param [String] string a string line containing a treatment field definition
620
- #
621
- def treatment_field(string)
622
- f = Field.load(string, @current_parent)
623
- @current_parent = f
624
- end
625
-
626
- # Creates a simulation field record from the given string.
627
- #
628
- # @param [String] string a string line containing a simulation field definition
629
- #
630
- def simulation_field(string)
631
- sf = SimulationField.load(string, @current_parent)
632
- @current_parent = sf
633
- end
634
-
635
- end
636
-
1
+ # Copyright 2011-2020 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
+
637
646
  end