Class: TransmissionBase

Inherits:
GEDCOMBase show all
Defined in:
lib/gedcom/transmission_base.rb

Overview

TransmissionBase is a subclass of GEDCOMBase, and contains methods used by the parsing process to build the other Gedcom classes, instantiate instances for each GEDCOM record type, and populate the fields based on the parsed GEDCOM file.

Direct Known Subclasses

Transmission

Constant Summary collapse

ACTION =

action_handler process the actions in the GedcomParser::TAGS hash, creating classes and populating attributes. Actions: [:class, :class_name] inidicates this line, and any further data, will be stored in the class :class_name [:pop] indicates that data will now be stored in the previous class. [:field, :fieldname] indicates that the data part of the line will be stored in the field :field_name [:field, [:fieldname, value]] fieldname stores the given value. [:append, :fieldname] indicates that the data part of the line will be appended to this field [:append_nl, :fieldname] indicates that the data part of the line will be appended to this field, after first appending a nl [:xref, [:field, :record_type]] indicates that the xref value of the line will get stored in the named field and points to the record_type. [:key, :index_name] means we need to create an index entry, in the index index_name, for this items xref value. nil in this field indicates that we should ignore this TAG and its children.

0
DATA =
1

Instance Attribute Summary collapse

Attributes inherited from GEDCOMBase

#restriction

Instance Method Summary collapse

Methods inherited from GEDCOMBase

#changed, #changed?, #created?, #find, #locked?, no_tabs, #private?, #save, tabs, #to_db, #to_gedcom, #to_s, #to_s_ordered, #to_s_r, #token_to_s, #xref_check

Constructor Details

#initialize(*a) ⇒ TransmissionBase

new creates initializes the arrays for each of the GEDCOM level 0 record types.



20
21
22
23
24
25
26
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
# File 'lib/gedcom/transmission_base.rb', line 20

def initialize(*a)
  super(*a)
   #Create the initial top level arrays of records that can exist in a transmission.
  @header_record = []
  @submission_record = []
  @submitter_record = []
  @individual_record = []
  @family_record = []
  @source_record = []
  @repository_record = []
  @multimedia_record = []
  @note_record = []
  @trailer_record = []

  #Class_stack is for the parsing process to hold the classes we create as we walk down the levels of a gedcom record.
  #The class we are currently working with is on the top of the stack.
  #The put ourselves on the top of the stack. The number represents the number of class to pop to go back one level of gedcom.
  @class_stack = [[self, 0]]
  
  #Create a hash to hold the indexes used in this transmission
  #Set up default indexes for the known level 0 types, so we can reference them even if they have no members.
  @indexes = {
    :individual => {},
    :family => {},
    :note => {},
    :source => {},
    :repository => {},
    :multimedia => {},
    :submitter => {},
    :submission => {}
  }
end

Instance Attribute Details

#class_stackObject

Class_stack is for the parsing process to hold the classes we create as we walk down the levels of a gedcom record. The class we are currently working with is on the top of the stack. The put ourselves on the top of the stack. The number represents the number of class to pop to go back one level of gedcom.



12
13
14
# File 'lib/gedcom/transmission_base.rb', line 12

def class_stack
  @class_stack
end

#indexesObject

A hash to hold the indexes used in this transmission, each of which is also a hash (though that may change)



15
16
17
# File 'lib/gedcom/transmission_base.rb', line 15

def indexes
  @indexes
end

Instance Method Details

#action_handler(lineno, tokens, child_record = nil, min_occurances = 0, max_occurances = nil, data_type = nil, max_data_size = nil, action = nil, data_description = '') ⇒ Object



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
# File 'lib/gedcom/transmission_base.rb', line 254

def action_handler( lineno, tokens, child_record = nil, min_occurances = 0, max_occurances = nil, data_type = nil, max_data_size = nil, action = nil, data_description = '' )

  validate(lineno, tokens, max_data_size)

  if action != nil
    nclasses = 1
    new_class = nil
    action.each do |do_this| #We have instructions for handling this line.
      case do_this[ACTION]
        when :class 
          #create a new class, making an instance of it, and making the instance the default one.
          new_class = create_class(lineno, do_this[DATA])
          #Add this instance to an Array of records as an attribute named new_class of the current class.
          add_to_class_field(lineno, do_this[DATA], new_class)
          #Make this class the current one
          @class_stack << [new_class, nclasses]
          nclasses += 1 #We want to be able to unwind the stack in sync with the gedcom.
                        #Hence we need to know how many classes we created at each point.
        when :field
          if do_this[DATA].class == Array #Then the value we are storing is given, rather than derived from the source file.
            update_field(lineno, do_this[DATA][0], data_type, do_this[DATA][1])
          else
            update_field(lineno, do_this[DATA], data_type, tokens.data)
          end
        when :append_nl 
          #We want to add a line terminator, then append the new data field from tokens
          #e.g. We do this with CONT lines being appended to a NOTE.
          append_nl_field(lineno, do_this[DATA], data_type, tokens.data)
        when :append 
          #we want to append the data field in tokens, to the named field.
          #e.g. We do this with CONC lines being added to a NOTE.
          append_sp_field(lineno, do_this[DATA], data_type, tokens.data)
        when :xref 
          #we want to record the reference (xref) from the token object.
          add_to_index(lineno, do_this[DATA][0], do_this[DATA][1], tokens.xref)
        when :key
          #will only occur after a class that is the thing we want the xref index to point to. ie xref => new_class
          #All the level 0 indexes should exist already, as it helps to have them, even if they are empty.
          create_index(lineno, do_this[DATA], tokens.xref, new_class )
        when :pop
          @class_stack.pop #In this instance, we want to remove only the last item, even if there were several added.
        when :push
          @class_stack << @class_stack.last #We need to have dummy entries when the gedcom has another level, but we build no class for it.
      end
    end
  end    
end

#add_to_class_field(lineno, record, data) ⇒ Object

add_to_class_field() is part of the parsing process, and checks to see if the field exists in the current states, target object. If the record exists, then the data is added to the record array. Each record is an array, so we can have multiple instances of a TAG in a GEDCOM record. If the record doesn't exist, the record is added to the target object, along with attr_accessors. The data is then added as above. * lineno is the current GEDCOM files line number, so we can report errors. * record is a symbol naming the attribute we want to store the data in in the class. * data is a word array, holding the GEDCOM lines data value



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/gedcom/transmission_base.rb', line 112

def add_to_class_field(lineno, record, data)
  #puts "#{lineno}: Add class instance '#{data.class}' to record #{record} of #{@class_stack.last[0]}"
  if @class_stack.last[0].class.method_defined?(record) == false
    #record is missing from the class, so add it using attr_accessor, thus getting the get and set methods.
    #Unlikely to ever see this case, as we precreated all fields for all class needed for GEDCOM 5.5
    p "#{lineno}: create a record called #{record} in class #{@class_stack.last[0].class.to_s}" 
    @class_stack.last[0].class.class_eval("attr_accessor :#{record}")
    #p "#{lineno}: Add the class #{data.class.to_s} as an array, to the record #{record} in class #{@class_stack.last[0].class.to_s}" 
    @class_stack.last[0].send( record.to_s + "=", data ? [ data ] : []) #Much faster than eval("@class_stack.last[0].#{record} = [#{data}]")
  else
    #record exists, get a reference to it, so we can just add the data to the record's data array.
    if (a = @class_stack.last[0].send( record ) ) != nil
      #This way is much faster than eval("@class_stack.last[0].#{record} << #{data}")
      #p "#{lineno}: Add the class #{data.class.to_s} to the record #{record}[] in class #{@class_stack.last[0].class.to_s}" 
      a << data if data != nil
    else
      #Got a reference to the attribute in the class, but no value stored as yet.
      #p "#{lineno}: Add the class #{data.class.to_s} as an array, to the record #{record} in class #{@class_stack.last[0].class.to_s}" 
      @class_stack.last[0].send( record.to_s + "=", data ? [ data ] : [] )
    end
  end
end

#add_to_index(lineno, field_name, index_name, key) ⇒ Object

add_to_index is part of the parsing process, and adds references into the current target object to the index (i.e fills in the XREFs with index references]



234
235
236
237
238
239
# File 'lib/gedcom/transmission_base.rb', line 234

def add_to_index(lineno, field_name, index_name, key)
  #p "#{lineno}: Add key #{key} to index #{index_name} to field #{field_name} of #{@class_stack.last[0]}"
  #There are many cases where there can be multiple records of the same GEDCOM type, in the parent record, so we store these in an array.
  #e.g. CHIL xrefs can occur multiple times in FAM record.
   add_to_class_field(lineno, field_name, Xref.new(index_name, *key) )
end

#append_nl_field(lineno, field, data_type, data) ⇒ Object

append_nl_field is part of the parsing process, and inserts a 'n' character in front of the data, then calls append_to_field. * lineno is the current GEDCOM files line number, so we can report errors. * field is a symbol naming the attribute we want to store the data in in the class. * data is a word array, holding the GEDCOM lines data value * data_type gives hints to help validate the data (which we don't yet do).



172
173
174
175
176
177
# File 'lib/gedcom/transmission_base.rb', line 172

def append_nl_field(lineno, field, data_type, data)
  #p "#{lineno}: Append data '\n#{data}' to field #{field} of #{@class_stack.last[0]}"
  the_data = ["\n"] #want to add a new line to the existing data
  the_data += data if data != nil #add the new data only if it is not null.
  append_to_field(lineno, field, data_type, the_data)
end

#append_sp_field(lineno, field, data_type, data) ⇒ Object

append_sp_field is part of the parsing process, and inserts a ' ' character in front of the data, then calls append_to_field. * lineno is the current GEDCOM files line number, so we can report errors. * field is a symbol naming the attribute we want to store the data in in the class. * data is a word array, holding the GEDCOM lines data value * data_type gives hints to help validate the data (which we don't yet do).



160
161
162
163
164
165
# File 'lib/gedcom/transmission_base.rb', line 160

def append_sp_field(lineno, field, data_type, data)
  #p "#{lineno}: Append data ' #{data}' to field #{field} of #{@class_stack.last[0]}"
  the_data = [" "] #want to add a space to the existing data, before adding it to the string.
  the_data += data if data != nil #add the new data only if it is not null.
  append_to_field(lineno, field, data_type, the_data)
end

#append_to_field(lineno, field, data_type, data) ⇒ Object

append_to_field is part of the parsing process, and adds the data to the end of the field's array. Shared code between append_sp_field and append_nl_field. * lineno is the current GEDCOM files line number, so we can report errors. * field is a symbol naming the attribute we want to store the data in in the class. * data is a word array, holding the GEDCOM lines data value * data_type gives hints to help validate the data (which we don't yet do).



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/gedcom/transmission_base.rb', line 185

def append_to_field(lineno, field, data_type, data)
  #p "#{lineno}: Append data '#{data}' to field #{field} of #{@class_stack.last[0]}"
  begin
    if data != nil  #Only bother if we have some data to append
      if ( a = @class_stack.last[0].send( field ) ) != nil  #The field is not nil, then we need to add to the last value in the array
        if a != [] #The field is not an empty array
          if a[-1] != nil #The last element is not null, so we append to it.
            #p "Add the class #{data.class.to_s} to the field #{field}[] in class #{@class_stack.last[0].class.to_s}"
            a[-1] << GedString.new(data) 
          else #The last element is null, so we replace is, rather than append to it.
            #p "Add the class #{data.class.to_s} as an array, to the field #{field} in class #{@class_stack.last[0].class.to_s}"
            a[-1] = GedString.new(data)
          end
        else #Was an empty array, the data will be the first member in the array.
          a[0] = GedString.new(data)
        end
      else #The field was null. We need to set the first value to our data value, in an array.
        #p "Add the class #{data.class.to_s} as an array, to the field #{field} in class #{@class_stack.last[0].class.to_s}"
        @class_stack.last[0].send( field.to_s + '=',  [ GedString.new(data) ] )
      end
    end
  rescue => exception
    p "#{exception} : Append data '#{data}' to field '#{field}' of class '#{@class_stack.last[0].class}'"
    raise exception
  end
end

#create_class(lineno, class_name) ⇒ Object

create_class() is part of the parsing process, and looks in the ClassTracker class to see if this class exists. if it doesn't already exist, the class is created and an instance is returned. if it already exists, then a new instance of the class is returned.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/gedcom/transmission_base.rb', line 89

def create_class(lineno, class_name)
  if class_name != nil
    new_class = class_name.to_s.capitalize
    if defined(new_class) == nil
      p "#{lineno}: Create class #{new_class}"
      define(new_class)
      new_class = Object.const_set("#{new_class}", Class.new) #creates a Class and assigns it the constant given
    end 
    #p "#{lineno}: instance class #{class_name}"
    class_instance = eval "#{new_class}.new(self)" #create an instance of the new class.
    if class_instance == nil
      raise "class #{class_name} instance nil"
    end
  end
  class_instance
end

#create_index(lineno, index_name, key, value) ⇒ Object

create_index is part of the parsing process, and adds the key, value pair to the index index_name, creating the index if it didn't already exist. * lineno is the current GEDCOM files line number, so we can report errors. * index_name is the category of the index. e.g. it could be an index of individual records , or family records, etc. * key is a GEDCOM XREF we want to be able to track. * value is the target object that was created to hold the referenced GEDCOM record.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/gedcom/transmission_base.rb', line 217

def create_index(lineno, index_name, key, value)
  if index_name != nil
    if @indexes[index_name] == nil
      #puts "#{lineno}: create index #{index_name}"
      @indexes[index_name] = {} #empty hash
    end
    if key != nil
      #p "#{lineno}: Add (key,value) #{key} => #{@class_stack.last[0]} to index #{index_name}"
      if @indexes[index_name][key] != nil
        raise "duplicate key #{key} in index #{index_name}"
      end
      @indexes[index_name][key] = value
    end
  end
end

#define(class_name) ⇒ Object



306
307
308
# File 'lib/gedcom/transmission_base.rb', line 306

def define(class_name)
  ClassTracker <<  class_name
end

#defined(class_name) ⇒ Object



302
303
304
# File 'lib/gedcom/transmission_base.rb', line 302

def defined(class_name)
  ClassTracker::exists? class_name
end

#popObject

pop() is part of the parsing process, and removes target objects from their stack, which must remain aligned with the ParseState stack. Multiple object may be removed, as we may step back multiple levels in the ParseState stack. The object removed will have been placed into their parent GEDCOM records object before being removed from the stack.



138
139
140
141
142
# File 'lib/gedcom/transmission_base.rb', line 138

def pop
  #this is to catch multiple classes added to the stack, for one gedcom line.
  i = @class_stack.last[1]
  i.times { @class_stack.pop }
end

#summaryObject

summary() prints out the number of each level 0 record type the we have just parsed.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/gedcom/transmission_base.rb', line 54

def summary
  puts "HEAD count = #{@header_record.length}"
  puts "SUBM count = #{@submission_record.length}"
  puts "SUBN count = #{@submitter_record.length}"
  puts "INDI count = #{@individual_record.length}"
  puts "FAM count = #{@family_record.length}"
  puts "SOUR count = #{@source_record.length}"
  puts "REPO count = #{@repository_record.length}"
  puts "OBJE count = #{@multimedia_record.length}"
  puts "NOTE count = #{@note_record.length}"
  puts "TRLR count = #{@trailer_record.length}"
  
  #p ClassTracker #Debugging line.
  #pp @indexes[:individual]["IPB4"] #debugging test to find and print an indivual's record with xref @IPB4@
  #pp @indexes[:family]["F1"] #debugging test to find and print a family record with the xref @F1@
  #pp @indexes[:note] #debugging test to print all NOTE records
  #p find(:individual,"IB1024").to_gedcom #debugging test to find an individual record with xref @IB1024@ and print the record and its sub-records.
end

#update_field(lineno, field, data_type, data) ⇒ Object

update_field() is part of the parsing process, calls add_to_class_field. I'm sure there is a reason I did this. I just can't think what it was. * lineno is the current GEDCOM files line number, so we can report errors. * field is a symbol naming the attribute we want to store the data in in the class. * data is a word array, holding the GEDCOM lines data value * data_type gives hints to help validate the data (which we don't yet do).



150
151
152
153
# File 'lib/gedcom/transmission_base.rb', line 150

def update_field(lineno, field, data_type, data)
  #p "#{lineno}: Add data '#{data}' to field #{field} of #{@class_stack.last[0]}"
  add_to_class_field(lineno, field, data == nil ? nil : GedString.new(data))
end

#validate(lineno, tokens, max_data_size) ⇒ Object

validate() is part of the parsing process, and checks that the GEDCOM line falls within the data length specified by the standard A warning is printed if the data is longer than allowed by the standard. As most programs seem to ignore the maximum data lengths, including the test file from LDS, the line is accepted regardless.



76
77
78
79
80
81
82
83
84
# File 'lib/gedcom/transmission_base.rb', line 76

def validate lineno, tokens, max_data_size
  #Validate the length of the data field is in bounds.
  if tokens.data != nil
    length = tokens.data.inject(0) { |l,element| l += element.length + 1 }
    if length - 1 > max_data_size
      p "Warning Line #{lineno}: Data portion may be too long for some databases. (Length = #{length - 1}, MAX = #{max_data_size})"
    end
  end
end