gedcom 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +74 -0
  3. data/README.txt +129 -0
  4. data/Rakefile +13 -0
  5. data/lib/gedcom.rb +105 -0
  6. data/lib/gedcom/address_record.rb +77 -0
  7. data/lib/gedcom/adoption_record.rb +57 -0
  8. data/lib/gedcom/association_record.rb +79 -0
  9. data/lib/gedcom/cause_record.rb +45 -0
  10. data/lib/gedcom/change_date_record.rb +39 -0
  11. data/lib/gedcom/character_set_record.rb +41 -0
  12. data/lib/gedcom/citation_data_record.rb +49 -0
  13. data/lib/gedcom/citation_event_type_record.rb +42 -0
  14. data/lib/gedcom/corporate_record.rb +36 -0
  15. data/lib/gedcom/date_record.rb +206 -0
  16. data/lib/gedcom/encoded_line_record.rb +34 -0
  17. data/lib/gedcom/event_age_record.rb +36 -0
  18. data/lib/gedcom/event_record.rb +258 -0
  19. data/lib/gedcom/events_list_record.rb +61 -0
  20. data/lib/gedcom/families_individuals.rb +79 -0
  21. data/lib/gedcom/family_record.rb +89 -0
  22. data/lib/gedcom/gedcom_all.rb +41 -0
  23. data/lib/gedcom/gedcom_base.rb +337 -0
  24. data/lib/gedcom/gedcom_record.rb +44 -0
  25. data/lib/gedcom/header_data_record.rb +49 -0
  26. data/lib/gedcom/header_record.rb +75 -0
  27. data/lib/gedcom/header_source_record.rb +42 -0
  28. data/lib/gedcom/individual_attribute_record.rb +86 -0
  29. data/lib/gedcom/individual_record.rb +128 -0
  30. data/lib/gedcom/multimedia_citation_record.rb +55 -0
  31. data/lib/gedcom/multimedia_record.rb +72 -0
  32. data/lib/gedcom/name_record.rb +58 -0
  33. data/lib/gedcom/note_citation_record.rb +37 -0
  34. data/lib/gedcom/note_record.rb +61 -0
  35. data/lib/gedcom/place_record.rb +46 -0
  36. data/lib/gedcom/refn_record.rb +32 -0
  37. data/lib/gedcom/repository_caln.rb +33 -0
  38. data/lib/gedcom/repository_citation_record.rb +43 -0
  39. data/lib/gedcom/repository_record.rb +41 -0
  40. data/lib/gedcom/source_citation_record.rb +74 -0
  41. data/lib/gedcom/source_record.rb +84 -0
  42. data/lib/gedcom/source_scope_record.rb +35 -0
  43. data/lib/gedcom/submission_record.rb +53 -0
  44. data/lib/gedcom/submitter_record.rb +53 -0
  45. data/lib/gedcom/text_record.rb +31 -0
  46. data/lib/gedcom/trailer_record.rb +22 -0
  47. data/lib/gedcom/transmission.rb +103 -0
  48. data/lib/gedcom/transmission_base.rb +267 -0
  49. data/lib/parser/class_tracker.rb +33 -0
  50. data/lib/parser/ged_line.rb +99 -0
  51. data/lib/parser/gedcom_parser.rb +798 -0
  52. data/lib/parser/instruction.rb +14 -0
  53. data/lib/parser/parse_state.rb +49 -0
  54. data/test/test_gedcom.rb +7 -0
  55. data/test_data/Document.RTF +1 -0
  56. data/test_data/Document.tex +1 -0
  57. data/test_data/ImgFile.BMP +0 -0
  58. data/test_data/ImgFile.GIF +0 -0
  59. data/test_data/ImgFile.JPG +0 -0
  60. data/test_data/ImgFile.MAC +0 -0
  61. data/test_data/ImgFile.PCX +0 -0
  62. data/test_data/ImgFile.PIC +0 -0
  63. data/test_data/ImgFile.PNG +0 -0
  64. data/test_data/ImgFile.PSD +0 -0
  65. data/test_data/ImgFile.TGA +0 -0
  66. data/test_data/ImgFile.TIF +0 -0
  67. data/test_data/README.txt +1 -16
  68. data/test_data/TGC551.ged +1 -0
  69. data/test_data/TGC551LF.ged +2162 -0
  70. data/test_data/TGC55C.ged +1 -0
  71. data/test_data/TGC55CLF.ged +2202 -0
  72. data/test_data/force.wav +0 -0
  73. data/test_data/suntun.mov +0 -0
  74. data/test_data/top.mpg +0 -0
  75. metadata +140 -0
@@ -0,0 +1,84 @@
1
+ require 'gedcom_base.rb'
2
+
3
+ #Internal representation of the GEDCOM SOUR record type
4
+ #Both inline and references to Level 0 Source_records are referenced via the Source_citation_record class.
5
+ #
6
+ #SOURCE_RECORD:=
7
+ # 0 @<XREF:SOUR>@ SOUR {0:M}
8
+ # +1 DATA {0:1}
9
+ # +2 EVEN <EVENTS_RECORDED> {0:M}
10
+ # +3 DATE <DATE_PERIOD> {0:1}
11
+ # +3 PLAC <SOURCE_JURISDICTION_PLACE> {0:1}
12
+ # +2 AGNC <RESPONSIBLE_AGENCY> {0:1}
13
+ # +2 <<NOTE_STRUCTURE>> {0:M}
14
+ # +1 AUTH <SOURCE_ORIGINATOR> {0:1}
15
+ # +2 [CONT|CONC] <SOURCE_ORIGINATOR> {0:M}
16
+ # +1 TITL <SOURCE_DESCRIPTIVE_TITLE> {0:1}
17
+ # +2 [CONT|CONC] <SOURCE_DESCRIPTIVE_TITLE> {0:M}
18
+ # +1 ABBR <SOURCE_FILED_BY_ENTRY> {0:1}
19
+ # +1 PUBL <SOURCE_PUBLICATION_FACTS> {0:1}
20
+ # +2 [CONT|CONC] <SOURCE_PUBLICATION_FACTS> {0:M}
21
+ # +1 TEXT <TEXT_FROM_SOURCE> {0:1}
22
+ # +2 [CONT|CONC] <TEXT_FROM_SOURCE> {0:M}
23
+ # +1 <<SOURCE_REPOSITORY_CITATION>> {0:1}
24
+ # +1 <<MULTIMEDIA_LINK>> {0:M}
25
+ # +1 <<NOTE_STRUCTURE>> {0:M}
26
+ # +1 REFN <USER_REFERENCE_NUMBER> {0:M}
27
+ # +2 TYPE <USER_REFERENCE_TYPE> {0:1}
28
+ # +1 RIN <AUTOMATED_RECORD_ID> {0:1}
29
+ # +1 <<CHANGE_DATE>> {0:1}
30
+ #
31
+ # Source records are used to provide a bibliographic description of the source cited. (See the
32
+ # <<SOURCE_CITATION>> structure, page 32, which contains the pointer to this source record.)#
33
+ #
34
+ #Systems not using level 0 source records, inline SOUR records combining SOURCE_RECORDS with SOURCE_CITATIONs.
35
+ #We create both a Source_record object and a Source_citation_record object, as if the transmission had used both.
36
+ # n SOUR <SOURCE_DESCRIPTION> {1:1}
37
+ # +1 [ CONC | CONT ] <SOURCE_DESCRIPTION> {0:M}
38
+ # +1 TEXT <TEXT_FROM_SOURCE> {0:M}
39
+ # +2 [CONC | CONT ] <TEXT_FROM_SOURCE> {0:M}
40
+ # +1 <<NOTE_STRUCTURE>> {0:M}
41
+ #
42
+ #The attributes are all arrays representing the +1 level of the SOURCE_RECORD.
43
+ #* Those ending in _ref are GEDCOM XREF index keys
44
+ #* Those ending in _record are array of classes of that type.
45
+ #* The remainder are arrays of attributes that could be present in the SOUR records.
46
+ #
47
+ class Source_record < GEDCOMBase
48
+ attr_accessor :source_ref, :short_title, :title, :author, :source_scope_record, :publication_details
49
+ attr_accessor :repository_citation_record, :text_record, :note_citation_record, :multimedia_citation_record
50
+ attr_accessor :refn_record, :automated_record_id, :change_date_record
51
+
52
+ ClassTracker << :Source_record
53
+
54
+ #to_gedcom sets up the state engine arrays @this_level and @sub_level, which drive the parent class to_gedcom method generating GEDCOM output.
55
+ #There are two types of SOUR record, inline and reference, so this is done dynamically in to_gedcom rather than the initialize method.
56
+ #Probably should be two classes, rather than this conditional.
57
+ def to_gedcom(level=0)
58
+ if @source_ref != nil
59
+ @this_level = [ [:xref, "SOUR", :source_ref] ]
60
+ @sub_level = [ #level + 1
61
+ [:print, "ABBR", :short_title],
62
+ [:cont, "TITL", :title],
63
+ [:cont, "AUTH", :author],
64
+ [:cont, "PUBL", :publication_details],
65
+ [:walk, nil, :repository_citation_record],
66
+ [:walk, nil, :text_record],
67
+ [:walk, nil, :multimedia_citation_record],
68
+ [:walk, nil, :source_scope_record],
69
+ [:walk, nil, :note_citation_record],
70
+ [:walk, nil, :refn_record],
71
+ [:print, "RIN", :automated_record_id],
72
+ [:walk, nil, :change_date_record],
73
+ ]
74
+ else
75
+ @this_level = [ [:cont, "SOUR", :title] ]
76
+ @sub_level = [ #level + 1
77
+ [:walk, nil, :text_record],
78
+ [:walk, nil, :note_citation_record] ,
79
+ ]
80
+ end
81
+ super(level)
82
+ end
83
+ end
84
+
@@ -0,0 +1,35 @@
1
+ require 'gedcom_base.rb'
2
+
3
+ #Internal representation of the GEDCOM DATA record type, a record type under the GEDCOM Level 0 SOUR record type
4
+ #
5
+ #=SOURCE_RECORD:=
6
+ # 0 @<XREF:SOUR>@ SOUR {0:M}
7
+ # +1 DATA {0:1}
8
+ # +2 EVEN <EVENTS_RECORDED> {0:M}
9
+ # +3 DATE <DATE_PERIOD> {0:1}
10
+ # +3 PLAC <SOURCE_JURISDICTION_PLACE> {0:1}
11
+ # +2 AGNC <RESPONSIBLE_AGENCY> {0:1}
12
+ # +2 <<NOTE_STRUCTURE>> {0:M}
13
+ # ...
14
+ #
15
+ #The attributes are all arrays.
16
+ #* Those ending in _record are array of classes of that type.
17
+ #* The remainder are arrays of attributes that could be present in the SOUR.DATA records.
18
+
19
+ class Source_scope_record < GEDCOMBase
20
+ attr_accessor :events_list_record, :agency, :note_citation_record
21
+
22
+ ClassTracker << :Source_scope_record
23
+
24
+ #new sets up the state engine arrays @this_level and @sub_level, which drive the to_gedcom method generating GEDCOM output.
25
+ def initialize(*a)
26
+ super(*a)
27
+ @this_level = [[:nodata, "DATA", nil]]
28
+ @sub_level = [ #level + 1
29
+ [:walk, nil, :events_list_record],
30
+ [:print, "AGNC",:agency],
31
+ [:walk, nil, :note_citation_record],
32
+ ]
33
+ end
34
+ end
35
+
@@ -0,0 +1,53 @@
1
+ require 'gedcom_base.rb'
2
+
3
+ #Internal representation of the GEDCOM SUBN record type
4
+ #
5
+ #=SUBMISSION_RECORD:=
6
+ # 0 @XREF:SUBN@ SUBN {0:M}
7
+ # +1 SUBM @XREF:SUBM@ {0:1}
8
+ # +1 FAMF <NAME_OF_FAMILY_FILE> {0:1}
9
+ # +1 TEMP <TEMPLE_CODE> {0:1}
10
+ # +1 ANCE <GENERATIONS_OF_ANCESTORS> {0:1}
11
+ # +1 DESC <GENERATIONS_OF_DESCENDANTS> {0:1}
12
+ # +1 ORDI <ORDINANCE_PROCESS_FLAG> {0:1}
13
+ # +1 RIN <AUTOMATED_RECORD_ID> {0:1}
14
+ #
15
+ # I also recognise notes in this record, so I can handle user tags as notes.
16
+ # +1 <<NOTE_STRUCTURE>> {0:M}
17
+ #
18
+ # The sending system uses a submission record to send instructions and information to the receiving
19
+ # system. TempleReady processes submission records to determine which temple the cleared records
20
+ # should be directed to. The submission record is also used for communication between Ancestral File
21
+ # download requests and TempleReady. Each GEDCOM transmission file should have only one
22
+ # submission record. Multiple submissions are handled by creating separate GEDCOM transmission
23
+ #
24
+ #The attributes are all arrays.
25
+ #* Those ending in _ref are GEDCOM XREF index keys
26
+ #* Those ending in _record are array of classes of that type.
27
+ #* The remainder are arrays of attributes that could be present in the SUBN records.
28
+
29
+
30
+ class Submission_record < GEDCOMBase
31
+ attr_accessor :submission_ref, :submitter_ref, :lds_family_file, :lds_temple_code
32
+ attr_accessor :generations_of_ancestor, :generations_of_descendant, :automated_record_id
33
+ attr_accessor :process_ordinates, :note_citation_record
34
+
35
+ ClassTracker << :Submission_record
36
+
37
+ #new sets up the state engine arrays @this_level and @sub_level, which drive the to_gedcom method generating GEDCOM output.
38
+ def initialize(*a)
39
+ super(*a)
40
+ @this_level = [ [:xref, "SUBN", :submission_ref] ]
41
+ @sub_level = [ #level + 1
42
+ [:xref, "SUBM", :submitter_ref],
43
+ [:print, "FAMF", :lds_family_file ],
44
+ [:print, "TEMP", :lds_temple_code ],
45
+ [:print, "ANCE", :generations_of_ancestor ],
46
+ [:print, "DESC", :generations_of_descendant ],
47
+ [:print, "ORDI", :process_ordinates ],
48
+ [:print, "RIN", :automated_record_id ],
49
+ [:walk, nil, :note_citation_record ],
50
+ ]
51
+ end
52
+ end
53
+
@@ -0,0 +1,53 @@
1
+ require 'gedcom_base.rb'
2
+
3
+ #Internal representation of the GEDCOM SUBM record type
4
+ #
5
+ #=SUBMITTER_RECORD:=
6
+ # 0 @<XREF:SUBM>@ SUBM {0:M}
7
+ # +1 NAME <SUBMITTER_NAME> {1:1}
8
+ # +1 <<ADDRESS_STRUCTURE>> {0:1}
9
+ # +1 PHON <PHONE_NUMBER> {0:3} (defined in the Address structure)
10
+ # +1 <<MULTIMEDIA_LINK>> {0:M}
11
+ # +1 LANG <LANGUAGE_PREFERENCE> {0:3}
12
+ # +1 RFN <SUBMITTER_REGISTERED_RFN> {0:1}
13
+ # +1 RIN <AUTOMATED_RECORD_ID> {0:1}
14
+ # +1 <<CHANGE_DATE>> {0:1}
15
+ #
16
+ # I also recognise notes in this record, so I can handle user tags as notes.
17
+ # +1 <<NOTE_STRUCTURE>> {0:M}
18
+ #
19
+ # The submitter record identifies an individual or organization that contributed information contained
20
+ # in the GEDCOM transmission. All records in the transmission are assumed to be submitted by the
21
+ # SUBMITTER referenced in the HEADer, unless a SUBMitter reference inside a specific record
22
+ # points at a different SUBMITTER record.
23
+ #
24
+ #The attributes are all arrays.
25
+ #* Those ending in _ref are GEDCOM XREF index keys
26
+ #* Those ending in _record are array of classes of that type.
27
+ #* The remainder are arrays of attributes that could be present in the SUBM records.
28
+
29
+ class Submitter_record < GEDCOMBase
30
+ attr_accessor :submitter_ref, :name_record, :address_record, :phone, :multimedia_citation_record
31
+ attr_accessor :language_list, :lds_submitter_id, :automated_record_id, :change_date_record, :note_citation_record
32
+
33
+ ClassTracker << :Submitter_record
34
+
35
+ #new sets up the state engine arrays @this_level and @sub_level, which drive the to_gedcom method generating GEDCOM output.
36
+ def initialize(*a)
37
+ super(*a)
38
+ @this_level = [ [:xref, "SUBM", :submitter_ref] ]
39
+ @sub_level = [ #level + 1
40
+ [:walk, nil, :name_record ],
41
+ [:walk, nil, :address_record ],
42
+ [:print, "PHON", :phone ],
43
+ [:print, "LANG", :language_list ],
44
+ [:walk, nil, :multimedia_citation_record ],
45
+ [:walk, nil, :note_citation_record ],
46
+ [:print, "RFN", :lds_submitter_id ],
47
+ [:print, "RIN", :automated_record_id ],
48
+ [:walk, nil, :change_date_record ],
49
+ ]
50
+ end
51
+
52
+ end
53
+
@@ -0,0 +1,31 @@
1
+ require 'gedcom_base.rb'
2
+
3
+ #Internal representation of the GEDCOM TEXT record type.
4
+ #
5
+ #This tag is used in SOUR and SOUR.DATA records
6
+ #=TEXT
7
+ # n TEXT <TEXT_FROM_SOURCE> {0:M}
8
+ # +1 [CONC | CONT ] <TEXT_FROM_SOURCE> {0:M}
9
+ #
10
+ #==TEXT_FROM_SOURCE:= {Size=1:248}
11
+ # A verbatim copy of any description contained within the source. This indicates notes or text that are
12
+ # actually contained in the source document, not the submitter's opinion about the source. This should
13
+ # be, from the evidence point of view, "what the original record keeper said" as opposed to the
14
+ # researcher's interpretation. The word TEXT, in this case, means from the text which appeared in the
15
+ # source record including labels.
16
+ #
17
+
18
+ class Text_record < GEDCOMBase
19
+ attr_accessor :text
20
+
21
+ ClassTracker << :Text_record
22
+
23
+ #new sets up the state engine arrays @this_level and @sub_level, which drive the to_gedcom method generating GEDCOM output.
24
+ def initialize(*a)
25
+ super(*a)
26
+ @this_level = [ [:cont, "TEXT", :text] ]
27
+ @sub_level = [ #level + 1
28
+ ]
29
+ end
30
+ end
31
+
@@ -0,0 +1,22 @@
1
+ require 'gedcom_base.rb'
2
+
3
+ #Internal representation of the GEDCOM TRLR record that terminates transmissions.
4
+ #The Trailer_record class is just a place marker to ensure we have encountered a termination record in the GEDCOM file.
5
+ #
6
+ #=TRAILER:=
7
+ # 0 TRLR {1:1}
8
+ # At level 0, specifies the end of a GEDCOM transmission.
9
+
10
+ class Trailer_record < GEDCOMBase
11
+
12
+ ClassTracker << :Trailer_record
13
+
14
+ #new sets up the state engine arrays @this_level and @sub_level, which drive the to_gedcom method generating GEDCOM output.
15
+ def initialize(*a)
16
+ super(*a)
17
+ @this_level = [ [:nodata, "TRLR", nil] ]
18
+ @sub_level = [ #level + 1
19
+ ]
20
+ end
21
+ end
22
+
@@ -0,0 +1,103 @@
1
+ require 'gedcom_all.rb'
2
+
3
+ #Transmission subclasses TransmissionBase, providing a cleaner view for public consumption.
4
+ #
5
+ #TransmissionBase is a subclass of GEDCOMBase, and contains methods used by the parsing process
6
+ #to build the other Gedcom classes, instantiate instances for each GEDCOM record type, and populate
7
+ #the fields based on the parsed GEDCOM file.
8
+ #
9
+ #
10
+ #A Transmission object is encapsulating a LINEAGE_LINKED_GEDCOM. From the GEDCOM 5.5 standard:
11
+ #=LINEAGE_LINKED_GEDCOM:=
12
+ # This is a model of the lineage-linked GEDCOM structure for submitting data to other lineage-linked
13
+ # GEDCOM processing systems. A header and a trailer record are required, and they can enclose any
14
+ # number of data records. Tags from Appendix A (see page 67) must be used in the same context as
15
+ # shown in the following form. User defined tags (see <NEW_TAG> on page 46) are discouraged but
16
+ # when used must begin with an under-score.
17
+ # 0 <<HEADER>> {1:1}
18
+ # 0 <<SUBMISSION_RECORD>> {0:1}
19
+ # 0 <<FAM_RECORD>> {0:M}
20
+ # 0 <<INDIVIDUAL_RECORD>> {0:M}
21
+ # 0 <<MULTIMEDIA_RECORD>> {0:M}
22
+ # 0 <<NOTE_RECORD>> {0:M}
23
+ # 0 <<REPOSITORY_RECORD>> {0:M}
24
+ # 0 <<SOURCE_RECORD>> {0:M}
25
+ # 0 <<SUBMITTER_RECORD>> {0:M}
26
+ # 0 TRLR {1:1}
27
+ #
28
+ #Each of the classes attributes is an array of objects representing the level 0 GEDCOM records in the
29
+ #transmission. There is also an :index attribute defined in GEDCOMBase, with an associated
30
+ #find method (see Transmission#find)
31
+
32
+
33
+ class Transmission < TransmissionBase
34
+
35
+ attr_accessor :header_record, :submitter_record, :family_record, :individual_record, :source_record
36
+ attr_accessor :multimedia_record, :note_record, :repository_record, :submission_record, :trailer_record
37
+
38
+ def initialize(*a)
39
+ super(nil, *a[1..-1]) #don't need to put ourselves into @transmission.
40
+
41
+ @this_level = [ [:walk, nil, :header_record], #Recurse over the HEAD header records (should only be one)
42
+ [:walk, nil, :submission_record], #Recurse over the SUBN submission records
43
+ [:walk, nil, :submitter_record], #Recurse over the SUBM submitter record(s)
44
+ [:walk, nil, :source_record], #Recurse over the SOUR Source records
45
+ [:walk, nil, :repository_record], #Recurse over the REPO repository records
46
+ [:walk, nil, :family_record], #Recurse over the FAM Family records
47
+ [:walk, nil, :individual_record], #Recurse over the INDI Individual records
48
+ [:walk, nil, :multimedia_record], #Recurse over the OBJE multimedia records
49
+ [:walk, nil, :note_record], #Recurse over the NOTE records
50
+ [:walk, nil, :trailer_record], #Recurse over the Trailer Record(s). (should only be the one).
51
+ ]
52
+ @sub_level = [#level + 1
53
+ ]
54
+
55
+ end
56
+
57
+ #Looks in a transmissions indexes for an index called index_name, returning the value associated with the key.
58
+ #also used by xref_check to validate the XREF entries in a transmission really do point to valid level 0 records.
59
+ #Standard indexes are the same as the level 0 types that have XREF values:
60
+ #* :individual
61
+ #* :family
62
+ #* :note
63
+ #* :source
64
+ #* :repository
65
+ #* :multimedia
66
+ #* :submitter
67
+ #* :submission
68
+ #Keys in each of these indexes are the XREF values from the GEDCOM file.
69
+ #The values stored in the indexes are the actual objects that they refer to.
70
+ #* e.g.
71
+ # if (f = find(:individual,"I14") ) != nil then print f.to_gedcom end
72
+ # will find the Individual's record with "0 @I14@ INDI" and print the record and its sub-records.
73
+ def find(index_name, key)
74
+ if @indexes != nil && (index = @indexes[index_name.to_sym]) != nil
75
+ index[key]
76
+ else
77
+ nil
78
+ end
79
+ end
80
+
81
+ #debugging code to show what indexes were created and what keys are in each
82
+ #Printing out the index values generates too much output, so these are ignored.
83
+ def dump_indexes
84
+ @indexes.each do |key,value|
85
+ puts "Index #{key}"
86
+ value.each do |vkey, vvalue|
87
+ puts "\t#{vkey}"
88
+ end
89
+ end
90
+ end
91
+ =begin
92
+ def summary
93
+ @@tabs = true
94
+ if (f = find(:individual,"PERSON7") ) != nil
95
+ s = f.to_gedcom
96
+ puts s #output window copy gives an error.
97
+ File.open('/tmp/xx.txt','w') { |fd| fd.print s } #file copy is correct
98
+ puts s #output window copy gives an error.
99
+ end
100
+ @@tabs = false
101
+ end
102
+ =end
103
+ end
@@ -0,0 +1,267 @@
1
+ require 'pp'
2
+ require 'gedcom_base.rb'
3
+
4
+ #TransmissionBase is a subclass of GEDCOMBase, and contains methods used by the parsing process
5
+ #to build the other Gedcom classes, instantiate instances for each GEDCOM record type, and populate
6
+ #the fields based on the parsed GEDCOM file.
7
+ class TransmissionBase < GEDCOMBase
8
+
9
+ ClassTracker << :TransmissionBase
10
+
11
+ #new creates initializes the arrays for each of the GEDCOM level 0 record types.
12
+ def initialize(*a)
13
+ super(*a)
14
+ #Create the initial top level arrays of records that can exist in a transmission.
15
+ @header_record = []
16
+ @submission_record = []
17
+ @submitter_record = []
18
+ @individual_record = []
19
+ @family_record = []
20
+ @source_record = []
21
+ @repository_record = []
22
+ @multimedia_record = []
23
+ @note_record = []
24
+ @trailer_record = []
25
+
26
+ #Class_stack is for the parsing process to hold the classes we create as we walk down the levels of a gedcom record.
27
+ #The class we are currently working with is on the top of the stack.
28
+ #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.
29
+ @class_stack = [[self, 0]]
30
+ #Create a hash to hold the indexes used in this transmission
31
+ @indexes = {} #find is defined in GEDCOMBase, as it gets used by a private method of that class.
32
+ end
33
+
34
+ #summary() prints out the number of each level 0 record type the we have just parsed.
35
+ def summary
36
+ puts "HEAD count = #{@header_record.length}"
37
+ puts "SUBM count = #{@submission_record.length}"
38
+ puts "SUBN count = #{@submitter_record.length}"
39
+ puts "INDI count = #{@individual_record.length}"
40
+ puts "FAM count = #{@family_record.length}"
41
+ puts "SOUR count = #{@source_record.length}"
42
+ puts "REPO count = #{@repository_record.length}"
43
+ puts "OBJE count = #{@multimedia_record.length}"
44
+ puts "NOTE count = #{@note_record.length}"
45
+ puts "TRLR count = #{@trailer_record.length}"
46
+
47
+ #p ClassTracker #Debugging line.
48
+ #pp @indexes[:individual]["IPB4"] #debugging test to find and print an indivual's record with xref @IPB4@
49
+ #pp @indexes[:family]["F1"] #debugging test to find and print a family record with the xref @F1@
50
+ #pp @indexes[:note] #debugging test to print all NOTE records
51
+ #p find(:individual,"IB1024").to_gedcom #debugging test to find an individual record with xref @IB1024@ and print the record and its sub-records.
52
+ end
53
+
54
+ #validate() is part of the parsing process, and checks that the GEDCOM line falls within the data length specified by the standard
55
+ #A warning is printed if the data is longer than allowed by the standard. As most programs seem to ignore the maximum data lengths,
56
+ #including the test file from LDS, the line is accepted regardless.
57
+ def validate lineno, tokens, max_data_size
58
+ #Validate the length of the data field is in bounds.
59
+ if tokens.data != nil
60
+ length = tokens.data.inject(0) { |l,element| l += element.length + 1 }
61
+ if length - 1 > max_data_size
62
+ p "Warning Line #{lineno}: Data portion may be too long for some databases. (Length = #{length - 1}, MAX = #{max_data_size})"
63
+ end
64
+ end
65
+ end
66
+
67
+ #create_class() is part of the parsing process, and looks in the ClassTracker class to see if this class exists.
68
+ #if it doesn't already exist, the class is created and an instance is returned.
69
+ #if it already exists, then a new instance of the class is returned.
70
+ def create_class(lineno, class_name)
71
+ if class_name != nil
72
+ new_class = class_name.to_s.capitalize
73
+ if defined(new_class) == nil
74
+ p "#{lineno}: Create class #{new_class}"
75
+ define(new_class)
76
+ new_class = Object.const_set("#{new_class}", Class.new) #creates a Class and assigns it the constant given
77
+ end
78
+ #p "#{lineno}: instance class #{class_name}"
79
+ class_instance = eval "#{new_class}.new(self)" #create an instance of the new class.
80
+ if class_instance == nil
81
+ raise "class #{class_name} instance nil"
82
+ end
83
+ end
84
+ class_instance
85
+ end
86
+
87
+ #add_to_class_field() is part of the parsing process, and checks to see if the field exists in the current states, target object.
88
+ #If the field exists, then the data is added to the field array. Each field is an array, so we can have multiple instances of a TAG in a GEDCOM record.
89
+ #If the field doesn't exist, the field is added to the target object, along with attr_accessors. The data is then added as above.
90
+ #* lineno is the current GEDCOM files line number, so we can report errors.
91
+ #* field is a symbol naming the attribute we want to store the data in in the class.
92
+ #* data is a word array, holding the GEDCOM lines data value
93
+ def add_to_class_field(lineno, field, data)
94
+ #puts "#{lineno}: Add class instance '#{data.class}' to field #{field} of #{@class_stack.last[0]}"
95
+ if @class_stack.last[0].class.method_defined?(field) == false
96
+ p "#{lineno}: create a field called #{field} in class #{@class_stack.last[0].class.to_s}"
97
+ @class_stack.last[0].class.class_eval("attr_accessor :#{field}")
98
+ #p "#{lineno}: Add the class #{data.class.to_s} as an array, to the field #{field} in class #{@class_stack.last[0].class.to_s}"
99
+ @class_stack.last[0].send( field.to_s + "=", data ? [ data ] : []) #Much faster than eval("@class_stack.last[0].#{field} = [#{data}]")
100
+ else
101
+ if a = @class_stack.last[0].send( field )
102
+ #Much faster than eval("@class_stack.last[0].#{field} << #{data}")
103
+ #p "#{lineno}: Add the class #{data.class.to_s} to the field #{field}[] in class #{@class_stack.last[0].class.to_s}"
104
+ a << data if data != nil
105
+ else
106
+ #p "#{lineno}: Add the class #{data.class.to_s} as an array, to the field #{field} in class #{@class_stack.last[0].class.to_s}"
107
+ @class_stack.last[0].send( field.to_s + "=", data ? [ data ] : [] )
108
+ end
109
+ end
110
+ end
111
+
112
+ #pop() is part of the parsing process, and removes target objects from their stack, which must remain aligned with the ParseState stack.
113
+ #Multiple object may be removed, as we may step back multiple levels in the ParseState stack.
114
+ #The object removed will have been placed into their parent GEDCOM records object before being removed from the stack.
115
+ def pop
116
+ #this is to catch multiple classes added to the stack, for one gedcom line.
117
+ i = @class_stack.last[1]
118
+ i.times { @class_stack.pop }
119
+ end
120
+
121
+ #update_field() is part of the parsing process, and discards the data_type and calls add_to_class_field.
122
+ #I'm sure there is a reason I did this. I just can't think what it was.
123
+ #* lineno is the current GEDCOM files line number, so we can report errors.
124
+ #* field is a symbol naming the attribute we want to store the data in in the class.
125
+ #* data is a word array, holding the GEDCOM lines data value
126
+ def update_field(lineno, field, data_type, data)
127
+ #p "#{lineno}: Add data '#{data}' to field #{field} of #{@class_stack.last[0]}"
128
+ add_to_class_field(lineno, field, data)
129
+ end
130
+
131
+ #append_nl_field() is part of the parsing process, and inserts a '\n' character in front of the data, then calls append_field().
132
+ #* lineno is the current GEDCOM files line number, so we can report errors.
133
+ #* field is a symbol naming the attribute we want to store the data in in the class.
134
+ #* data is a word array, holding the GEDCOM lines data value
135
+ def append_nl_field(lineno, field, data_type, data)
136
+ #p "#{lineno}: Append data 'nl + #{data}' to field #{field} of #{@class_stack.last[0]}"
137
+ the_data = ["\n"] #want to add a new line to the existing data
138
+ the_data += data if data != nil #add the new data only if it is not null.
139
+ append_field(lineno, field,data_type, the_data)
140
+ end
141
+
142
+ #append_field() is part of the parsing process, and adds the data to the end of the field's array.
143
+ #* lineno is the current GEDCOM files line number, so we can report errors.
144
+ #* field is a symbol naming the attribute we want to store the data in in the class.
145
+ #* data is a word array, holding the GEDCOM lines data value
146
+ def append_field(lineno, field, data_type, data)
147
+ #p "#{lineno}: Append data '#{data}' to field #{field} of #{@class_stack.last[0]}"
148
+ begin
149
+ if data != nil#Only bother if we have some data
150
+ if a = @class_stack.last[0].send( field ) #The field is not nil
151
+ if a != [] #The field is not an empty array
152
+ if a[-1] != nil #The last element is not null
153
+ #p "Add the class #{data.class.to_s} to the field #{field}[] in class #{@class_stack.last[0].class.to_s}"
154
+ a[-1] += data
155
+ else #The last element is null
156
+ #p "Add the class #{data.class.to_s} as an array, to the field #{field} in class #{@class_stack.last[0].class.to_s}"
157
+ a[-1] = data
158
+ end
159
+ else #Was an empty array, so add the element.
160
+ a[0] = data
161
+ end
162
+ else #The field was null
163
+ #p "Add the class #{data.class.to_s} as an array, to the field #{field} in class #{@class_stack.last[0].class.to_s}"
164
+ @class_stack.last[0].send( field.to_s + '=', [data])
165
+ end
166
+ end
167
+ rescue => exception
168
+ p "#{exception} : Append data '#{data}' to field '#{field}' of class '#{@class_stack.last[0]}'"
169
+ raise exception
170
+ end
171
+ end
172
+
173
+ #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.
174
+ #* lineno is the current GEDCOM files line number, so we can report errors.
175
+ #* index_name is the category of the index. e.g. it could be an index of individual records , or family records, etc.
176
+ #* key is a GEDCOM XREF we want to be able to track.
177
+ #* value is the target object that was created to hold the referenced GEDCOM record.
178
+ def create_index(lineno, index_name, key, value)
179
+ if index_name != nil
180
+ if @indexes[index_name] == nil
181
+ #puts "#{lineno}: create index #{index_name}"
182
+ @indexes[index_name] = {} #empty hash
183
+ end
184
+ if key != nil
185
+ #p "#{lineno}: Add (key,value) #{key} => #{@class_stack.last[0]} to index #{index_name}"
186
+ if @indexes[index_name][key] != nil
187
+ raise "duplicate key #{key} in index #{index_name}"
188
+ end
189
+ @indexes[index_name][key] = value
190
+ end
191
+ end
192
+ end
193
+
194
+ #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]
195
+ def add_to_index(lineno, field_name, index_name, key)
196
+ #p "#{lineno}: Add key #{key} to index #{index_name} to field #{field_name} of #{@class_stack.last[0]}"
197
+ add_to_class_field(lineno, field_name, [index_name, *key] )
198
+ end
199
+
200
+ #action_handler process the actions in the GedcomParser::TAGS hash, creating classes and populating attributes.
201
+ # Actions:
202
+ # [:class, :class_name] inidicates this line, and any further data, will be stored in the class :class_name
203
+ # [:pop] indicates that data will now be stored in the previous class.
204
+ # [:field, :fieldname] indicates that the data part of the line will be stored in the field :field_name
205
+ # [:field, [:fieldname, value]] fieldname stores the given value.
206
+ # [:append, :fieldname] indicates that the data part of the line will be appended to this field
207
+ # [:append_nl, :fieldname] indicates that the data part of the line will be appended to this field, after first appending a nl
208
+ # [: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.
209
+ # [:key, :index_name] means we need to create an index entry, in the index index_name, for this items xref value.
210
+ # nil in this field indicates that we should ignore this TAG and its children.
211
+ ACTION = 0
212
+ DATA = 1
213
+ 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 = '' )
214
+
215
+ validate(lineno, tokens, max_data_size)
216
+
217
+ if action != nil
218
+ nclasses = 1
219
+ new_class = nil
220
+ action.each do |do_this| #We have instructions for handling this line.
221
+ case do_this[ACTION]
222
+ when :class
223
+ #create a new class, making an instance of it, and making the instance the default one.
224
+ new_class = create_class(lineno, do_this[DATA])
225
+ #Add this instance to an Array field of the current class, of the same name as the new class.
226
+ add_to_class_field(lineno, do_this[DATA], new_class)
227
+ #Make this class the current one
228
+ @class_stack << [new_class, nclasses]
229
+ nclasses += 1 #We want to be able to unwind the stack in sync with the gedcom.
230
+ #Hence we need to know how many classes we created at each point.
231
+ when :field
232
+ if do_this[DATA].class == Array #Then the value we are storing is given, rather than derived from the source file.
233
+ update_field(lineno, do_this[DATA][0], data_type, do_this[DATA][1])
234
+ else
235
+ update_field(lineno, do_this[DATA], data_type, tokens.data)
236
+ end
237
+ when :append_nl
238
+ #We want to add a line terminator, then append the new data field from tokens
239
+ append_nl_field(lineno, do_this[DATA], data_type, tokens.data)
240
+ when :append
241
+ #we want to append the data field in tokens, to the named field.
242
+ append_field(lineno, do_this[DATA], data_type, tokens.data)
243
+ when :xref
244
+ #we want to record the reference (xref) from the token object.
245
+ add_to_index(lineno, do_this[DATA][0], do_this[DATA][1], tokens.xref)
246
+ when :key
247
+ #will only occur after a class that is the thing we want the xref index to point to. ie xref => new_class
248
+ create_index(lineno, do_this[DATA], tokens.xref, new_class )
249
+ when :pop
250
+ @class_stack.pop #In this instance, we want to remove only the last item, even if there were several added.
251
+ when :push
252
+ @class_stack << @class_stack.last #We need to have dummy entries when the gedcom has another level, but we build no class for it.
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ def defined(class_name)
259
+ ClassTracker::exists? class_name
260
+ end
261
+
262
+ def define(class_name)
263
+ ClassTracker << class_name
264
+ end
265
+
266
+ end
267
+