gedcom 0.9.0

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.
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
+