gedcom 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +74 -0
- data/README.txt +129 -0
- data/Rakefile +13 -0
- data/lib/gedcom.rb +105 -0
- data/lib/gedcom/address_record.rb +77 -0
- data/lib/gedcom/adoption_record.rb +57 -0
- data/lib/gedcom/association_record.rb +79 -0
- data/lib/gedcom/cause_record.rb +45 -0
- data/lib/gedcom/change_date_record.rb +39 -0
- data/lib/gedcom/character_set_record.rb +41 -0
- data/lib/gedcom/citation_data_record.rb +49 -0
- data/lib/gedcom/citation_event_type_record.rb +42 -0
- data/lib/gedcom/corporate_record.rb +36 -0
- data/lib/gedcom/date_record.rb +206 -0
- data/lib/gedcom/encoded_line_record.rb +34 -0
- data/lib/gedcom/event_age_record.rb +36 -0
- data/lib/gedcom/event_record.rb +258 -0
- data/lib/gedcom/events_list_record.rb +61 -0
- data/lib/gedcom/families_individuals.rb +79 -0
- data/lib/gedcom/family_record.rb +89 -0
- data/lib/gedcom/gedcom_all.rb +41 -0
- data/lib/gedcom/gedcom_base.rb +337 -0
- data/lib/gedcom/gedcom_record.rb +44 -0
- data/lib/gedcom/header_data_record.rb +49 -0
- data/lib/gedcom/header_record.rb +75 -0
- data/lib/gedcom/header_source_record.rb +42 -0
- data/lib/gedcom/individual_attribute_record.rb +86 -0
- data/lib/gedcom/individual_record.rb +128 -0
- data/lib/gedcom/multimedia_citation_record.rb +55 -0
- data/lib/gedcom/multimedia_record.rb +72 -0
- data/lib/gedcom/name_record.rb +58 -0
- data/lib/gedcom/note_citation_record.rb +37 -0
- data/lib/gedcom/note_record.rb +61 -0
- data/lib/gedcom/place_record.rb +46 -0
- data/lib/gedcom/refn_record.rb +32 -0
- data/lib/gedcom/repository_caln.rb +33 -0
- data/lib/gedcom/repository_citation_record.rb +43 -0
- data/lib/gedcom/repository_record.rb +41 -0
- data/lib/gedcom/source_citation_record.rb +74 -0
- data/lib/gedcom/source_record.rb +84 -0
- data/lib/gedcom/source_scope_record.rb +35 -0
- data/lib/gedcom/submission_record.rb +53 -0
- data/lib/gedcom/submitter_record.rb +53 -0
- data/lib/gedcom/text_record.rb +31 -0
- data/lib/gedcom/trailer_record.rb +22 -0
- data/lib/gedcom/transmission.rb +103 -0
- data/lib/gedcom/transmission_base.rb +267 -0
- data/lib/parser/class_tracker.rb +33 -0
- data/lib/parser/ged_line.rb +99 -0
- data/lib/parser/gedcom_parser.rb +798 -0
- data/lib/parser/instruction.rb +14 -0
- data/lib/parser/parse_state.rb +49 -0
- data/test/test_gedcom.rb +7 -0
- data/test_data/Document.RTF +1 -0
- data/test_data/Document.tex +1 -0
- data/test_data/ImgFile.BMP +0 -0
- data/test_data/ImgFile.GIF +0 -0
- data/test_data/ImgFile.JPG +0 -0
- data/test_data/ImgFile.MAC +0 -0
- data/test_data/ImgFile.PCX +0 -0
- data/test_data/ImgFile.PIC +0 -0
- data/test_data/ImgFile.PNG +0 -0
- data/test_data/ImgFile.PSD +0 -0
- data/test_data/ImgFile.TGA +0 -0
- data/test_data/ImgFile.TIF +0 -0
- data/test_data/README.txt +1 -16
- data/test_data/TGC551.ged +1 -0
- data/test_data/TGC551LF.ged +2162 -0
- data/test_data/TGC55C.ged +1 -0
- data/test_data/TGC55CLF.ged +2202 -0
- data/test_data/force.wav +0 -0
- data/test_data/suntun.mov +0 -0
- data/test_data/top.mpg +0 -0
- 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
|
+
|