ruby-hl7 0.2.50 → 0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +2 -2
- data/lib/ruby-hl7.rb +639 -621
- data/lib/segments/evn.rb +8 -8
- data/lib/segments/msa.rb +7 -7
- data/lib/segments/msh.rb +19 -19
- data/lib/segments/nte.rb +5 -5
- data/lib/segments/obr.rb +48 -47
- data/lib/segments/obx.rb +20 -19
- data/lib/segments/oru.rb +1 -1
- data/lib/segments/pid.rb +39 -39
- data/lib/segments/pv1.rb +53 -53
- data/lib/segments/pv2.rb +49 -49
- data/lib/segments/qrd.rb +13 -13
- data/lib/segments/qrf.rb +11 -11
- data/test/test_basic_parsing.rb +12 -1
- data/test/test_child_segment.rb +1 -1
- data/test/test_default_segment.rb +1 -1
- data/test/test_dynamic_segment_def.rb +1 -1
- data/test/test_msa_segment.rb +1 -1
- data/test/test_obr_segment.rb +5 -5
- data/test/test_obx_segment.rb +1 -1
- data/test/test_pid_segment.rb +1 -1
- data/test_data/empty_segments.hl7 +1 -1
- metadata +65 -64
data/README
CHANGED
@@ -6,14 +6,14 @@ The version id can be found in the HL7::VERSION constant.
|
|
6
6
|
|
7
7
|
* Bug tracking: http://trac.hasno.info/ruby-hl7
|
8
8
|
* Subversion: svn://hasno.info/ruby-hl7
|
9
|
+
* Git: git://github.com/segfault/ruby-hl7.git
|
9
10
|
* Docs: http://ruby-hl7.rubyforge.org
|
10
11
|
* Rubyforge: http://rubyforge.org/projects/ruby-hl7
|
11
12
|
* Lists
|
12
13
|
* Developers: mailto:ruby-hl7-devel@rubyforge.org
|
13
14
|
* Users: mailto:ruby-hl7-users@rubyforge.org
|
14
15
|
|
15
|
-
Copyright (c) 2006-
|
16
|
-
$Id: README 26 2007-03-18 14:41:41Z segfault $
|
16
|
+
Copyright (c) 2006-2008 Mark Guzman
|
17
17
|
|
18
18
|
== Download and Installation
|
19
19
|
Install the gem using the following command:
|
data/lib/ruby-hl7.rb
CHANGED
@@ -1,621 +1,639 @@
|
|
1
|
-
#= ruby-hl7.rb
|
2
|
-
# Ruby HL7 is designed to provide a simple, easy to use library for
|
3
|
-
# parsing and generating HL7 (2.x) messages.
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# Author: Mark Guzman (mailto:segfault@hasno.info)
|
7
|
-
#
|
8
|
-
# Copyright: (c) 2006-2007 Mark Guzman
|
9
|
-
#
|
10
|
-
# License: BSD
|
11
|
-
#
|
12
|
-
# $Id
|
13
|
-
#
|
14
|
-
# == License
|
15
|
-
# see the LICENSE file
|
16
|
-
#
|
17
|
-
|
18
|
-
require 'rubygems'
|
19
|
-
require "stringio"
|
20
|
-
require "date"
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# msh
|
56
|
-
# msh.
|
57
|
-
# msh.
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
attr :
|
79
|
-
attr :
|
80
|
-
|
81
|
-
|
82
|
-
#
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
@
|
87
|
-
@
|
88
|
-
@
|
89
|
-
@
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
#
|
100
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
ret =
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
# value
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
#
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
#
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
@segments
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
@segments
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
197
|
-
|
198
|
-
# provide a HL7 spec version of the message
|
199
|
-
def to_hl7
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
pre_mllp
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
#
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
segs =
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
end
|
240
|
-
|
241
|
-
def
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
end
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
#
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
#
|
311
|
-
#
|
312
|
-
#
|
313
|
-
#
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
#
|
420
|
-
def
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
#
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
#
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
end
|
558
|
-
|
559
|
-
def
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
@elements[ idx ]
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
#
|
594
|
-
#
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
#
|
608
|
-
#
|
609
|
-
#
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
#
|
619
|
-
|
620
|
-
|
621
|
-
#
|
1
|
+
#= ruby-hl7.rb
|
2
|
+
# Ruby HL7 is designed to provide a simple, easy to use library for
|
3
|
+
# parsing and generating HL7 (2.x) messages.
|
4
|
+
#
|
5
|
+
#
|
6
|
+
# Author: Mark Guzman (mailto:segfault@hasno.info)
|
7
|
+
#
|
8
|
+
# Copyright: (c) 2006-2007 Mark Guzman
|
9
|
+
#
|
10
|
+
# License: BSD
|
11
|
+
#
|
12
|
+
# $Id$
|
13
|
+
#
|
14
|
+
# == License
|
15
|
+
# see the LICENSE file
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require "stringio"
|
20
|
+
require "date"
|
21
|
+
|
22
|
+
module HL7 # :nodoc:
|
23
|
+
VERSION = "0.3"
|
24
|
+
def self.ParserConfig
|
25
|
+
@parser_cfg ||= { :empty_segment_is_error => true }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Encapsulate HL7 specific exceptions
|
30
|
+
class HL7::Exception < StandardError
|
31
|
+
end
|
32
|
+
|
33
|
+
# Parsing failed
|
34
|
+
class HL7::ParseError < HL7::Exception
|
35
|
+
end
|
36
|
+
|
37
|
+
# Attempting to use an invalid indice
|
38
|
+
class HL7::RangeError < HL7::Exception
|
39
|
+
end
|
40
|
+
|
41
|
+
# Attempting to assign invalid data to a field
|
42
|
+
class HL7::InvalidDataError < HL7::Exception
|
43
|
+
end
|
44
|
+
|
45
|
+
# Ruby Object representation of an hl7 2.x message
|
46
|
+
# the message object is actually a "smart" collection of hl7 segments
|
47
|
+
# == Examples
|
48
|
+
#
|
49
|
+
# ==== Creating a new HL7 message
|
50
|
+
#
|
51
|
+
# # create a message
|
52
|
+
# msg = HL7::Message.new
|
53
|
+
#
|
54
|
+
# # create a MSH segment for our new message
|
55
|
+
# msh = HL7::Message::Segment::MSH.new
|
56
|
+
# msh.recv_app = "ruby hl7"
|
57
|
+
# msh.recv_facility = "my office"
|
58
|
+
# msh.processing_id = rand(10000).to_s
|
59
|
+
#
|
60
|
+
# msg << msh # add the MSH segment to the message
|
61
|
+
#
|
62
|
+
# puts msg.to_s # readable version of the message
|
63
|
+
#
|
64
|
+
# puts msg.to_hl7 # hl7 version of the message (as a string)
|
65
|
+
#
|
66
|
+
# puts msg.to_mllp # mllp version of the message (as a string)
|
67
|
+
#
|
68
|
+
# ==== Parse an existing HL7 message
|
69
|
+
#
|
70
|
+
# raw_input = open( "my_hl7_msg.txt" ).readlines
|
71
|
+
# msg = HL7::Message.new( raw_input )
|
72
|
+
#
|
73
|
+
# puts "message type: %s" % msg[:MSH].message_type
|
74
|
+
#
|
75
|
+
#
|
76
|
+
class HL7::Message
|
77
|
+
include Enumerable # we treat an hl7 2.x message as a collection of segments
|
78
|
+
attr :element_delim
|
79
|
+
attr :item_delim
|
80
|
+
attr :segment_delim
|
81
|
+
|
82
|
+
# setup a new hl7 message
|
83
|
+
# raw_msg:: is an optional object containing an hl7 message
|
84
|
+
# it can either be a string or an Enumerable object
|
85
|
+
def initialize( raw_msg=nil, &blk )
|
86
|
+
@segments = []
|
87
|
+
@segments_by_name = {}
|
88
|
+
@item_delim = "^"
|
89
|
+
@element_delim = '|'
|
90
|
+
@segment_delim = "\r"
|
91
|
+
|
92
|
+
parse( raw_msg ) if raw_msg
|
93
|
+
|
94
|
+
if block_given?
|
95
|
+
blk.call self
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# access a segment of the message
|
100
|
+
# index:: can be a Range, Fixnum or anything that
|
101
|
+
# responds to to_sym
|
102
|
+
def []( index )
|
103
|
+
ret = nil
|
104
|
+
|
105
|
+
if index.kind_of?(Range) || index.kind_of?(Fixnum)
|
106
|
+
ret = @segments[ index ]
|
107
|
+
elsif (index.respond_to? :to_sym)
|
108
|
+
ret = @segments_by_name[ index.to_sym ]
|
109
|
+
ret = ret.first if ret && ret.length == 1
|
110
|
+
end
|
111
|
+
|
112
|
+
ret
|
113
|
+
end
|
114
|
+
|
115
|
+
# modify a segment of the message
|
116
|
+
# index:: can be a Range, Fixnum or anything that
|
117
|
+
# responds to to_sym
|
118
|
+
# value:: an HL7::Message::Segment object
|
119
|
+
def []=( index, value )
|
120
|
+
unless ( value && value.kind_of?(HL7::Message::Segment) )
|
121
|
+
raise HL7::Exception.new( "attempting to assign something other than an HL7 Segment" )
|
122
|
+
end
|
123
|
+
|
124
|
+
if index.kind_of?(Range) || index.kind_of?(Fixnum)
|
125
|
+
@segments[ index ] = value
|
126
|
+
elsif index.respond_to?(:to_sym)
|
127
|
+
(@segments_by_name[ index.to_sym ] ||= []) << value
|
128
|
+
else
|
129
|
+
raise HL7::Exception.new( "attempting to use an indice that is not a Range, Fixnum or to_sym providing object" )
|
130
|
+
end
|
131
|
+
|
132
|
+
value.segment_parent = self
|
133
|
+
end
|
134
|
+
|
135
|
+
# return the index of the value if it exists, nil otherwise
|
136
|
+
# value:: is expected to be a string
|
137
|
+
def index( value )
|
138
|
+
return nil unless (value && value.respond_to?(:to_sym))
|
139
|
+
|
140
|
+
segs = @segments_by_name[ value.to_sym ]
|
141
|
+
return nil unless segs
|
142
|
+
|
143
|
+
@segments.index( segs.to_a.first )
|
144
|
+
end
|
145
|
+
|
146
|
+
# add a segment to the message
|
147
|
+
# * will force auto set_id sequencing for segments containing set_id's
|
148
|
+
def <<( value )
|
149
|
+
unless ( value && value.kind_of?(HL7::Message::Segment) )
|
150
|
+
raise HL7::Exception.new( "attempting to append something other than an HL7 Segment" )
|
151
|
+
end
|
152
|
+
|
153
|
+
value.segment_parent = self unless value.segment_parent
|
154
|
+
(@segments ||= []) << value
|
155
|
+
name = value.class.to_s.gsub("HL7::Message::Segment::", "").to_sym
|
156
|
+
(@segments_by_name[ name ] ||= []) << value
|
157
|
+
sequence_segments unless @parsing # let's auto-set the set-id as we go
|
158
|
+
end
|
159
|
+
|
160
|
+
# parse a String or Enumerable object into an HL7::Message if possible
|
161
|
+
# * returns a new HL7::Message if successful
|
162
|
+
def self.parse( inobj )
|
163
|
+
HL7::Message.new do |msg|
|
164
|
+
msg.parse( inobj )
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# parse the provided String or Enumerable object into this message
|
169
|
+
def parse( inobj )
|
170
|
+
unless inobj.kind_of?(String) || inobj.respond_to?(:each)
|
171
|
+
raise HL7::ParseError.new
|
172
|
+
end
|
173
|
+
|
174
|
+
if inobj.kind_of?(String)
|
175
|
+
parse_string( inobj )
|
176
|
+
elsif inobj.respond_to?(:each)
|
177
|
+
parse_enumerable( inobj )
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# yield each segment in the message
|
182
|
+
def each # :yeilds: segment
|
183
|
+
return unless @segments
|
184
|
+
@segments.each { |s| yield s }
|
185
|
+
end
|
186
|
+
|
187
|
+
# return the segment count
|
188
|
+
def length
|
189
|
+
0 unless @segments
|
190
|
+
@segments.length
|
191
|
+
end
|
192
|
+
|
193
|
+
# provide a screen-readable version of the message
|
194
|
+
def to_s
|
195
|
+
@segments.collect { |s| s if s.to_s.length > 0 }.join( "\n" )
|
196
|
+
end
|
197
|
+
|
198
|
+
# provide a HL7 spec version of the message
|
199
|
+
def to_hl7
|
200
|
+
@segments.collect { |s| s if s.to_s.length > 0 }.join( @segment_delim )
|
201
|
+
end
|
202
|
+
|
203
|
+
# provide the HL7 spec version of the message wrapped in MLLP
|
204
|
+
def to_mllp
|
205
|
+
pre_mllp = to_hl7
|
206
|
+
"\x0b" + pre_mllp + "\x1c\r"
|
207
|
+
end
|
208
|
+
|
209
|
+
# auto-set the set_id fields of any message segments that
|
210
|
+
# provide it and have more than one instance in the message
|
211
|
+
def sequence_segments(base=nil)
|
212
|
+
last = nil
|
213
|
+
segs = @segments
|
214
|
+
segs = base.children if base
|
215
|
+
|
216
|
+
segs.each do |s|
|
217
|
+
if s.kind_of?( last.class ) && s.respond_to?( :set_id )
|
218
|
+
last.set_id = 1 unless last.set_id && last.set_id.to_i > 0
|
219
|
+
s.set_id = last.set_id.to_i + 1
|
220
|
+
end
|
221
|
+
|
222
|
+
if s.respond_to?(:children)
|
223
|
+
sequence_segments( s )
|
224
|
+
end
|
225
|
+
|
226
|
+
last = s
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
# Get the element delimiter from an MSH segment
|
232
|
+
def parse_element_delim(str)
|
233
|
+
(str && str.kind_of?(String)) ? str.slice(3,1) : "|"
|
234
|
+
end
|
235
|
+
|
236
|
+
# Get the item delimiter from an MSH segment
|
237
|
+
def parse_item_delim(str)
|
238
|
+
(str && str.kind_of?(String)) ? str.slice(4,1) : "^"
|
239
|
+
end
|
240
|
+
|
241
|
+
def parse_enumerable( inary )
|
242
|
+
#assumes an enumeration of strings....
|
243
|
+
inary.each do |oary|
|
244
|
+
parse_string( oary.to_s )
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def parse_string( instr )
|
249
|
+
post_mllp = instr
|
250
|
+
if /\x0b((:?.|\r|\n)+)\x1c\r/.match( instr )
|
251
|
+
post_mllp = $1 #strip the mllp bytes
|
252
|
+
end
|
253
|
+
|
254
|
+
ary = post_mllp.split( segment_delim, -1 )
|
255
|
+
generate_segments( ary )
|
256
|
+
end
|
257
|
+
|
258
|
+
def generate_segments( ary )
|
259
|
+
raise HL7::ParseError.new unless ary.length > 0
|
260
|
+
|
261
|
+
@parsing = true
|
262
|
+
last_seg = nil
|
263
|
+
ary.each do |elm|
|
264
|
+
if elm.slice(0,3) == "MSH"
|
265
|
+
@item_delim = parse_item_delim(elm)
|
266
|
+
@element_delim = parse_element_delim(elm)
|
267
|
+
end
|
268
|
+
last_seg = generate_segment( elm, last_seg ) || last_seg
|
269
|
+
end
|
270
|
+
@parsing = nil
|
271
|
+
end
|
272
|
+
|
273
|
+
def generate_segment( elm, last_seg )
|
274
|
+
seg_parts = elm.split( @element_delim, -1 )
|
275
|
+
unless seg_parts && (seg_parts.length > 0)
|
276
|
+
raise HL7::ParseError.new if HL7.ParserConfig[:empty_segment_is_error] || false
|
277
|
+
return nil
|
278
|
+
end
|
279
|
+
|
280
|
+
seg_name = seg_parts[0]
|
281
|
+
if HL7::Message::Segment.constants.index(seg_name) # do we have an implementation?
|
282
|
+
kls = eval("HL7::Message::Segment::%s" % seg_name)
|
283
|
+
else
|
284
|
+
# we don't have an implementation for this segment
|
285
|
+
# so lets just preserve the data
|
286
|
+
kls = HL7::Message::Segment::Default
|
287
|
+
end
|
288
|
+
new_seg = kls.new( elm, [@element_delim, @item_delim] )
|
289
|
+
new_seg.segment_parent = self
|
290
|
+
|
291
|
+
if last_seg && last_seg.respond_to?(:children) && last_seg.accepts?( seg_name )
|
292
|
+
last_seg.children << new_seg
|
293
|
+
new_seg.is_child_segment = true
|
294
|
+
return last_seg
|
295
|
+
end
|
296
|
+
|
297
|
+
@segments << new_seg
|
298
|
+
|
299
|
+
# we want to allow segment lookup by name
|
300
|
+
if seg_name && (seg_name.strip.length > 0)
|
301
|
+
seg_sym = seg_name.to_sym
|
302
|
+
@segments_by_name[ seg_sym ] ||= []
|
303
|
+
@segments_by_name[ seg_sym ] << new_seg
|
304
|
+
end
|
305
|
+
|
306
|
+
new_seg
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# Ruby Object representation of an hl7 2.x message segment
|
311
|
+
# The segments can be setup to provide aliases to specific fields with
|
312
|
+
# optional validation code that is run when the field is modified
|
313
|
+
# The segment field data is also accessible via the e<number> method.
|
314
|
+
#
|
315
|
+
# == Defining a New Segment
|
316
|
+
# class HL7::Message::Segment::NK1 < HL7::Message::Segment
|
317
|
+
# wieght 100 # segments are sorted ascendingly
|
318
|
+
# add_field :something_you_want # assumes :idx=>1
|
319
|
+
# add_field :something_else, :idx=>6 # :idx=>6 and field count=6
|
320
|
+
# add_field :something_more # :idx=>7
|
321
|
+
# add_field :block_example do |value|
|
322
|
+
# raise HL7::InvalidDataError.new unless value.to_i < 100 && value.to_i > 10
|
323
|
+
# return value
|
324
|
+
# end
|
325
|
+
# # this block will be executed when seg.block_example= is called
|
326
|
+
# # and when seg.block_example is called
|
327
|
+
#
|
328
|
+
class HL7::Message::Segment
|
329
|
+
attr :segment_parent, true
|
330
|
+
attr :element_delim
|
331
|
+
attr :item_delim
|
332
|
+
attr :segment_weight
|
333
|
+
|
334
|
+
# setup a new HL7::Message::Segment
|
335
|
+
# raw_segment:: is an optional String or Array which will be used as the
|
336
|
+
# segment's field data
|
337
|
+
# delims:: an optional array of delimiters, where
|
338
|
+
# delims[0] = element delimiter
|
339
|
+
# delims[1] = item delimiter
|
340
|
+
def initialize(raw_segment="", delims=[], &blk)
|
341
|
+
@segments_by_name = {}
|
342
|
+
@field_total = 0
|
343
|
+
@is_child = false
|
344
|
+
|
345
|
+
@element_delim = (delims.kind_of?(Array) && delims.length>0) ? delims[0] : "|"
|
346
|
+
@item_delim = (delims.kind_of?(Array) && delims.length>1) ? delims[1] : "^"
|
347
|
+
|
348
|
+
if (raw_segment.kind_of? Array)
|
349
|
+
@elements = raw_segment
|
350
|
+
else
|
351
|
+
@elements = raw_segment.split( @element_delim, -1 )
|
352
|
+
if raw_segment == ""
|
353
|
+
@elements[0] = self.class.to_s.split( "::" ).last
|
354
|
+
@elements << ""
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
if block_given?
|
359
|
+
callctx = eval( "self", blk )
|
360
|
+
def callctx.__seg__(val=nil)
|
361
|
+
@__seg_val__ ||= val
|
362
|
+
end
|
363
|
+
callctx.__seg__(self)
|
364
|
+
# TODO: find out if this pollutes the calling namespace permanently...
|
365
|
+
|
366
|
+
to_do = <<-END
|
367
|
+
def method_missing( sym, *args, &blk )
|
368
|
+
__seg__.send( sym, args, blk )
|
369
|
+
end
|
370
|
+
END
|
371
|
+
|
372
|
+
eval( to_do, blk )
|
373
|
+
yield self
|
374
|
+
eval( "undef method_missing", blk )
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def to_info
|
379
|
+
"%s: empty segment >> %s" % [ self.class.to_s, @elements.inspect ]
|
380
|
+
end
|
381
|
+
|
382
|
+
# output the HL7 spec version of the segment
|
383
|
+
def to_s
|
384
|
+
@elements.join( @element_delim )
|
385
|
+
end
|
386
|
+
|
387
|
+
# at the segment level there is no difference between to_s and to_hl7
|
388
|
+
alias :to_hl7 :to_s
|
389
|
+
|
390
|
+
# handle the e<number> field accessor
|
391
|
+
# and any aliases that didn't get added to the system automatically
|
392
|
+
def method_missing( sym, *args, &blk )
|
393
|
+
base_str = sym.to_s.gsub( "=", "" )
|
394
|
+
base_sym = base_str.to_sym
|
395
|
+
|
396
|
+
if self.class.fields.include?( base_sym )
|
397
|
+
# base_sym is ok, let's move on
|
398
|
+
elsif /e([0-9]+)/.match( base_str )
|
399
|
+
# base_sym should actually be $1, since we're going by
|
400
|
+
# element id number
|
401
|
+
base_sym = $1.to_i
|
402
|
+
else
|
403
|
+
super
|
404
|
+
end
|
405
|
+
|
406
|
+
if sym.to_s.include?( "=" )
|
407
|
+
write_field( base_sym, args )
|
408
|
+
else
|
409
|
+
|
410
|
+
if args.length > 0
|
411
|
+
write_field( base_sym, args )
|
412
|
+
else
|
413
|
+
read_field( base_sym )
|
414
|
+
end
|
415
|
+
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# sort-compare two Segments, 0 indicates equality
|
420
|
+
def <=>( other )
|
421
|
+
return nil unless other.kind_of?(HL7::Message::Segment)
|
422
|
+
|
423
|
+
# per Comparable docs: http://www.ruby-doc.org/core/classes/Comparable.html
|
424
|
+
diff = self.weight - other.weight
|
425
|
+
return -1 if diff > 0
|
426
|
+
return 1 if diff < 0
|
427
|
+
return 0
|
428
|
+
end
|
429
|
+
|
430
|
+
# get the defined sort-weight of this segment class
|
431
|
+
# an alias for self.weight
|
432
|
+
def weight
|
433
|
+
self.class.weight
|
434
|
+
end
|
435
|
+
|
436
|
+
|
437
|
+
# return true if the segment has a parent
|
438
|
+
def is_child_segment?
|
439
|
+
(@is_child_segment ||= false)
|
440
|
+
end
|
441
|
+
|
442
|
+
# indicate whether or not the segment has a parent
|
443
|
+
def is_child_segment=(val)
|
444
|
+
@is_child_segment = val
|
445
|
+
end
|
446
|
+
|
447
|
+
# get the length of the segment (number of fields it contains)
|
448
|
+
def length
|
449
|
+
0 unless @elements
|
450
|
+
@elements.length
|
451
|
+
end
|
452
|
+
|
453
|
+
|
454
|
+
private
|
455
|
+
def self.singleton #:nodoc:
|
456
|
+
class << self; self end
|
457
|
+
end
|
458
|
+
|
459
|
+
# DSL element to define a segment's sort weight
|
460
|
+
# returns the segment's current weight by default
|
461
|
+
# segments are sorted ascending
|
462
|
+
def self.weight(new_weight=nil)
|
463
|
+
if new_weight
|
464
|
+
singleton.module_eval do
|
465
|
+
@my_weight = new_weight
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
singleton.module_eval do
|
470
|
+
return 999 unless @my_weight
|
471
|
+
@my_weight
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
|
476
|
+
# allows a segment to store other segment objects
|
477
|
+
# used to handle associated lists like one OBR to many OBX segments
|
478
|
+
def self.has_children(child_types)
|
479
|
+
@child_types = child_types
|
480
|
+
define_method(:child_types) do
|
481
|
+
@child_types
|
482
|
+
end
|
483
|
+
|
484
|
+
self.class_eval do
|
485
|
+
define_method(:children) do
|
486
|
+
unless @my_children
|
487
|
+
p = self
|
488
|
+
@my_children ||= []
|
489
|
+
@my_children.instance_eval do
|
490
|
+
@parental = p
|
491
|
+
alias :old_append :<<
|
492
|
+
|
493
|
+
def <<(value)
|
494
|
+
unless (value && value.kind_of?(HL7::Message::Segment))
|
495
|
+
raise HL7::Exception.new( "attempting to append non-segment to a segment list" )
|
496
|
+
end
|
497
|
+
|
498
|
+
value.segment_parent = @parental
|
499
|
+
k = @parental
|
500
|
+
while (k && k.segment_parent && !k.segment_parent.kind_of?(HL7::Message))
|
501
|
+
k = k.segment_parent
|
502
|
+
end
|
503
|
+
k.segment_parent << value if k && k.segment_parent
|
504
|
+
old_append( value )
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
@my_children
|
510
|
+
end
|
511
|
+
|
512
|
+
define_method('accepts?') do |t|
|
513
|
+
t = t.to_sym if t && (t.to_s.length > 0) && t.respond_to?(:to_sym)
|
514
|
+
child_types.index t
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
# define a field alias
|
520
|
+
# * name is the alias itself (required)
|
521
|
+
# * options is a hash of parameters
|
522
|
+
# * :id is the field number to reference (optional, auto-increments from 1
|
523
|
+
# by default)
|
524
|
+
# * :blk is a validation proc (optional, overrides the second argument)
|
525
|
+
# * blk is an optional validation proc which MUST take a parameter
|
526
|
+
# and always return a value for the field (it will be used on read/write
|
527
|
+
# calls)
|
528
|
+
def self.add_field( name, options={}, &blk )
|
529
|
+
options = { :idx =>-1, :blk =>blk}.merge!( options )
|
530
|
+
name ||= :id
|
531
|
+
namesym = name.to_sym
|
532
|
+
@field_cnt ||= 1
|
533
|
+
if options[:idx] == -1
|
534
|
+
options[:idx] = @field_cnt # provide default auto-incrementing
|
535
|
+
end
|
536
|
+
@field_cnt = options[:idx].to_i + 1
|
537
|
+
|
538
|
+
singleton.module_eval do
|
539
|
+
@fields ||= {}
|
540
|
+
@fields[ namesym ] = options
|
541
|
+
end
|
542
|
+
|
543
|
+
self.class_eval <<-END
|
544
|
+
def #{name}(val=nil)
|
545
|
+
unless val
|
546
|
+
read_field( :#{namesym} )
|
547
|
+
else
|
548
|
+
write_field( :#{namesym}, val )
|
549
|
+
val # this matches existing n= method functionality
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def #{name}=(value)
|
554
|
+
write_field( :#{namesym}, value )
|
555
|
+
end
|
556
|
+
END
|
557
|
+
end
|
558
|
+
|
559
|
+
def self.fields #:nodoc:
|
560
|
+
singleton.module_eval do
|
561
|
+
(@fields ||= [])
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
def field_info( name ) #:nodoc:
|
566
|
+
field_blk = nil
|
567
|
+
idx = name # assume we've gotten a fixnum
|
568
|
+
unless name.kind_of?( Fixnum )
|
569
|
+
fld_info = self.class.fields[ name ]
|
570
|
+
idx = fld_info[:idx].to_i
|
571
|
+
field_blk = fld_info[:blk]
|
572
|
+
end
|
573
|
+
|
574
|
+
[ idx, field_blk ]
|
575
|
+
end
|
576
|
+
|
577
|
+
def read_field( name ) #:nodoc:
|
578
|
+
idx, field_blk = field_info( name )
|
579
|
+
return nil unless idx
|
580
|
+
return nil if (idx >= @elements.length)
|
581
|
+
|
582
|
+
ret = @elements[ idx ]
|
583
|
+
ret = ret.first if (ret.kind_of?(Array) && ret.length == 1)
|
584
|
+
ret = field_blk.call( ret ) if field_blk
|
585
|
+
ret
|
586
|
+
end
|
587
|
+
|
588
|
+
def write_field( name, value ) #:nodoc:
|
589
|
+
idx, field_blk = field_info( name )
|
590
|
+
return nil unless idx
|
591
|
+
|
592
|
+
if (idx >= @elements.length)
|
593
|
+
# make some space for the incoming field, missing items are assumed to
|
594
|
+
# be empty, so this is valid per the spec -mg
|
595
|
+
missing = ("," * (idx-@elements.length)).split(',',-1)
|
596
|
+
@elements += missing
|
597
|
+
end
|
598
|
+
|
599
|
+
value = field_blk.call( value ) if field_blk
|
600
|
+
@elements[ idx ] = value.to_s
|
601
|
+
end
|
602
|
+
|
603
|
+
@elements = []
|
604
|
+
|
605
|
+
end
|
606
|
+
|
607
|
+
# parse an hl7 formatted date
|
608
|
+
#def Date.from_hl7( hl7_date )
|
609
|
+
#end
|
610
|
+
|
611
|
+
#def Date.to_hl7_short( ruby_date )
|
612
|
+
#end
|
613
|
+
|
614
|
+
#def Date.to_hl7_med( ruby_date )
|
615
|
+
#end
|
616
|
+
|
617
|
+
#def Date.to_hl7_long( ruby_date )
|
618
|
+
#end
|
619
|
+
|
620
|
+
# Provide a catch-all information preserving segment
|
621
|
+
# * no aliases are not provided BUT you can use the numeric element accessor
|
622
|
+
#
|
623
|
+
# seg = HL7::Message::Segment::Default.new
|
624
|
+
# seg.e0 = "NK1"
|
625
|
+
# seg.e1 = "SOMETHING ELSE"
|
626
|
+
# seg.e2 = "KIN HERE"
|
627
|
+
#
|
628
|
+
class HL7::Message::Segment::Default < HL7::Message::Segment
|
629
|
+
def initialize(raw_segment="", delims=[])
|
630
|
+
segs = [] if (raw_segment == "")
|
631
|
+
segs ||= raw_segment
|
632
|
+
super( segs, delims )
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
# load our segments
|
637
|
+
Dir["#{File.dirname(__FILE__)}/segments/*.rb"].each { |ext| load ext }
|
638
|
+
|
639
|
+
# vim:tw=78:sw=2:ts=2:et:fdm=marker:
|