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