gramps-gedcom7 0.0.3__tar.gz
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.
- gramps_gedcom7-0.0.3/LICENSE +21 -0
- gramps_gedcom7-0.0.3/PKG-INFO +17 -0
- gramps_gedcom7-0.0.3/README.md +3 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/__init__.py +1 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/event.py +110 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/family.py +100 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/header.py +15 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/importer.py +35 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/individual.py +214 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/multimedia.py +53 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/note.py +26 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/process.py +106 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/repository.py +56 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/source.py +117 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/submitter.py +18 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/types.py +7 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7/util.py +168 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7.egg-info/PKG-INFO +17 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7.egg-info/SOURCES.txt +23 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7.egg-info/dependency_links.txt +1 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7.egg-info/requires.txt +2 -0
- gramps_gedcom7-0.0.3/gramps_gedcom7.egg-info/top_level.txt +1 -0
- gramps_gedcom7-0.0.3/pyproject.toml +24 -0
- gramps_gedcom7-0.0.3/setup.cfg +4 -0
- gramps_gedcom7-0.0.3/test/test_importer.py +10 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 David Straub
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gramps_gedcom7
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: A GEDCOM 7 support library for Gramps genealogy software
|
|
5
|
+
Author-email: David Straub <straub@protonmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DavidMStraub/gramps-gedcom7
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: gramps>=6.0.0
|
|
12
|
+
Requires-Dist: gedcom7>=0.3.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# gramps-gedcom7
|
|
16
|
+
|
|
17
|
+
A GEDCOM 7 import library for Gramps.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .importer import import_gedcom
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Process GEDCOM 7 event data."""
|
|
2
|
+
|
|
3
|
+
from gedcom7 import const as g7const
|
|
4
|
+
from gedcom7 import grammar as g7grammar
|
|
5
|
+
from gedcom7 import types as g7types
|
|
6
|
+
from gedcom7 import util as g7util
|
|
7
|
+
from gramps.gen.lib import Event, EventType, Place, PlaceName
|
|
8
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
9
|
+
|
|
10
|
+
from . import util
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def handle_event(
|
|
14
|
+
structure: g7types.GedcomStructure,
|
|
15
|
+
xref_handle_map: dict[str, str],
|
|
16
|
+
event_type_map: dict[str, int],
|
|
17
|
+
) -> tuple[Event, list[BasicPrimaryObject]]:
|
|
18
|
+
"""Convert a GEDCOM event structure to a Gramps Event object.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
structure: The GEDCOM structure containing the event data.
|
|
22
|
+
xref_handle_map: A map of XREFs to Gramps handles.
|
|
23
|
+
event_type_map: A mapping of GEDCOM event tags to Gramps EventType values.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A tuple containing the Gramps Event object and a list of additional objects created.
|
|
27
|
+
"""
|
|
28
|
+
event = Event()
|
|
29
|
+
event.set_type(event_type_map.get(structure.tag, EventType.CUSTOM))
|
|
30
|
+
event.handle = util.make_handle()
|
|
31
|
+
objects = []
|
|
32
|
+
for child in structure.children:
|
|
33
|
+
if child.tag == g7const.SNOTE and child.pointer != g7grammar.voidptr:
|
|
34
|
+
try:
|
|
35
|
+
note_handle = xref_handle_map[child.pointer]
|
|
36
|
+
except KeyError:
|
|
37
|
+
raise ValueError(f"Shared note {child.pointer} not found")
|
|
38
|
+
event.add_note(note_handle)
|
|
39
|
+
elif child.tag == g7const.NOTE:
|
|
40
|
+
event, note = util.add_note_to_object(child, event)
|
|
41
|
+
objects.append(note)
|
|
42
|
+
# TODO handle media
|
|
43
|
+
# TODO handle source citation
|
|
44
|
+
elif child.tag == g7const.PLAC:
|
|
45
|
+
place, other_objects = handle_place(child, xref_handle_map)
|
|
46
|
+
event.set_place_handle(place.handle)
|
|
47
|
+
objects.append(place)
|
|
48
|
+
objects.extend(other_objects)
|
|
49
|
+
elif child.tag == g7const.DATE:
|
|
50
|
+
assert isinstance(
|
|
51
|
+
child.value,
|
|
52
|
+
(
|
|
53
|
+
g7types.Date,
|
|
54
|
+
g7types.DatePeriod,
|
|
55
|
+
g7types.DateApprox,
|
|
56
|
+
g7types.DateRange,
|
|
57
|
+
),
|
|
58
|
+
), "Expected value to be a date-related object"
|
|
59
|
+
date = util.gedcom_date_value_to_gramps_date(child.value)
|
|
60
|
+
event.set_date_object(date)
|
|
61
|
+
return event, objects
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def handle_place(
|
|
65
|
+
structure: g7types.GedcomStructure,
|
|
66
|
+
xref_handle_map: dict[str, str],
|
|
67
|
+
) -> tuple[Place, list[BasicPrimaryObject]]:
|
|
68
|
+
"""Convert a GEDCOM place structure to a Gramps Place object.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
structure: The GEDCOM structure containing the place data.
|
|
72
|
+
xref_handle_map: A map of XREFs to Gramps handles.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
A Gramps Place object created from the GEDCOM structure.
|
|
76
|
+
"""
|
|
77
|
+
place = Place()
|
|
78
|
+
objects = []
|
|
79
|
+
place.handle = util.make_handle()
|
|
80
|
+
if structure.value:
|
|
81
|
+
name = PlaceName()
|
|
82
|
+
name.set_value(structure.value)
|
|
83
|
+
place.set_name(name)
|
|
84
|
+
for child in structure.children:
|
|
85
|
+
if child.tag == g7const.MAP:
|
|
86
|
+
lat = g7util.get_first_child_with_tag(child, g7const.LATI)
|
|
87
|
+
lon = g7util.get_first_child_with_tag(child, g7const.LONG)
|
|
88
|
+
if lat is not None and lon is not None:
|
|
89
|
+
if not isinstance(lat.value, str) or not isinstance(lon.value, str):
|
|
90
|
+
raise ValueError("Latitude and longitude must be strings")
|
|
91
|
+
place.set_latitude(lat.value)
|
|
92
|
+
place.set_longitude(lon.value)
|
|
93
|
+
elif child.tag == g7const.LANG and child.value:
|
|
94
|
+
place.name.set_language(child.value)
|
|
95
|
+
elif child.tag == g7const.TRAN:
|
|
96
|
+
alt_name = PlaceName()
|
|
97
|
+
alt_name.set_value(child.value)
|
|
98
|
+
if lang := g7util.get_first_child_with_tag(child, g7const.LANG):
|
|
99
|
+
alt_name.set_language(lang.value)
|
|
100
|
+
elif child.tag == g7const.SNOTE and child.pointer != g7grammar.voidptr:
|
|
101
|
+
try:
|
|
102
|
+
note_handle = xref_handle_map[child.pointer]
|
|
103
|
+
except KeyError:
|
|
104
|
+
raise ValueError(f"Shared note {child.pointer} not found")
|
|
105
|
+
place.add_note(note_handle)
|
|
106
|
+
elif child.tag == g7const.NOTE:
|
|
107
|
+
place, note = util.add_note_to_object(child, place)
|
|
108
|
+
# Add the note to the list of objects to be returned
|
|
109
|
+
objects.append(note)
|
|
110
|
+
return place, objects
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Handle GEDCOM family records and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from gedcom7 import const as g7const
|
|
6
|
+
from gedcom7 import grammar as g7grammar
|
|
7
|
+
from gedcom7 import types as g7types
|
|
8
|
+
from gramps.gen.lib import ChildRef, ChildRefType, Family, MediaRef, EventRef, EventType
|
|
9
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
10
|
+
|
|
11
|
+
from . import util
|
|
12
|
+
from .event import handle_event
|
|
13
|
+
|
|
14
|
+
EVENT_TYPE_MAP = {
|
|
15
|
+
g7const.ANUL: EventType.ANNULMENT,
|
|
16
|
+
g7const.CENS: EventType.CENSUS,
|
|
17
|
+
g7const.DIV: EventType.DIVORCE,
|
|
18
|
+
g7const.DIVF: EventType.DIV_FILING,
|
|
19
|
+
g7const.ENGA: EventType.ENGAGEMENT,
|
|
20
|
+
g7const.MARB: EventType.MARR_BANNS,
|
|
21
|
+
g7const.MARC: EventType.MARR_CONTR,
|
|
22
|
+
g7const.MARL: EventType.MARR_LIC,
|
|
23
|
+
g7const.MARS: EventType.MARR_SETTL,
|
|
24
|
+
g7const.EVEN: EventType.CUSTOM,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def handle_family(
|
|
29
|
+
structure: g7types.GedcomStructure, xref_handle_map: dict[str, str]
|
|
30
|
+
) -> List[BasicPrimaryObject]:
|
|
31
|
+
"""Handle an family record and convert it to Gramps objects.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
structure: The GEDCOM note structure to handle.
|
|
35
|
+
xref_handle_map: A map of XREFs to Gramps handles.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
A list of Gramps objects created from the GEDCOM structure.
|
|
39
|
+
"""
|
|
40
|
+
family = Family()
|
|
41
|
+
objects = []
|
|
42
|
+
for child in structure.children:
|
|
43
|
+
# TODO handle RESN
|
|
44
|
+
# TODO handle family attributes
|
|
45
|
+
if child.tag == g7const.HUSB and child.pointer != g7grammar.voidptr:
|
|
46
|
+
person_handle = xref_handle_map.get(child.pointer)
|
|
47
|
+
if not person_handle:
|
|
48
|
+
raise ValueError(f"Person {child.pointer} not found")
|
|
49
|
+
family.set_father_handle(person_handle)
|
|
50
|
+
# TODO handle HUSB PHRASE
|
|
51
|
+
elif child.tag == g7const.WIFE and child.pointer != g7grammar.voidptr:
|
|
52
|
+
person_handle = xref_handle_map.get(child.pointer)
|
|
53
|
+
if not person_handle:
|
|
54
|
+
raise ValueError(f"Person {child.pointer} not found")
|
|
55
|
+
family.set_mother_handle(person_handle)
|
|
56
|
+
# TODO handle WIFE PHRASE
|
|
57
|
+
elif child.tag == g7const.CHIL and child.pointer != g7grammar.voidptr:
|
|
58
|
+
person_handle = xref_handle_map.get(child.pointer)
|
|
59
|
+
if not person_handle:
|
|
60
|
+
raise ValueError(f"Child {child.pointer} not found")
|
|
61
|
+
child_ref = ChildRef()
|
|
62
|
+
child_ref.ref = person_handle
|
|
63
|
+
family.add_child_ref(child_ref)
|
|
64
|
+
# TODO handle CHIL PHRASE
|
|
65
|
+
# TODO handle associations
|
|
66
|
+
elif child.tag == g7const.SNOTE and child.pointer != g7grammar.voidptr:
|
|
67
|
+
try:
|
|
68
|
+
note_handle = xref_handle_map[child.pointer]
|
|
69
|
+
except KeyError:
|
|
70
|
+
raise ValueError(f"Shared note {child.pointer} not found")
|
|
71
|
+
family.add_note(note_handle)
|
|
72
|
+
elif child.tag == g7const.NOTE:
|
|
73
|
+
family, note = util.add_note_to_object(child, family)
|
|
74
|
+
objects.append(note)
|
|
75
|
+
# TODO handle source citations
|
|
76
|
+
# TODO handle indentifier
|
|
77
|
+
elif child.tag == g7const.OBJE:
|
|
78
|
+
media_ref = MediaRef()
|
|
79
|
+
if child.pointer != g7grammar.voidptr:
|
|
80
|
+
media_handle = xref_handle_map.get(child.pointer)
|
|
81
|
+
if not media_handle:
|
|
82
|
+
raise ValueError(f"Multimedia object {child.pointer} not found")
|
|
83
|
+
media_ref.ref = media_handle
|
|
84
|
+
# TODO crop is not implement yet
|
|
85
|
+
# one complication is that GEDCOM uses pixels, Gramps uses fractions.
|
|
86
|
+
# Consequently, image dimensions need to be known to convert.
|
|
87
|
+
family.add_media_reference(media_ref)
|
|
88
|
+
elif child.tag in EVENT_TYPE_MAP:
|
|
89
|
+
event, other_objects = handle_event(
|
|
90
|
+
child, xref_handle_map=xref_handle_map, event_type_map=EVENT_TYPE_MAP
|
|
91
|
+
)
|
|
92
|
+
objects.extend(other_objects)
|
|
93
|
+
event_ref = EventRef()
|
|
94
|
+
event_ref.ref = event.handle
|
|
95
|
+
family.add_event_ref(event_ref)
|
|
96
|
+
objects.append(event)
|
|
97
|
+
family = util.add_ids(family, structure=structure, xref_handle_map=xref_handle_map)
|
|
98
|
+
util.set_change_date(structure=structure, obj=family)
|
|
99
|
+
objects.append(family)
|
|
100
|
+
return objects
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Handle GEDCOM header records and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from gramps.gen.db import DbWriteBase
|
|
4
|
+
from gedcom7 import types as g7types
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def handle_header(structure: g7types.GedcomStructure, db: DbWriteBase):
|
|
8
|
+
"""Handle a header record and import it into the Gramps database.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
structure: The GEDCOM header structure to handle.
|
|
12
|
+
db: The Gramps database to import into.
|
|
13
|
+
"""
|
|
14
|
+
# TODO: Implement header import
|
|
15
|
+
pass
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Import a GEDCOM file into a Gramps database."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from gramps.gen.db import DbWriteBase
|
|
6
|
+
import gedcom7
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TextIO, BinaryIO
|
|
9
|
+
|
|
10
|
+
from . import process
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def import_gedcom(file_input: str | Path | TextIO | BinaryIO, db: DbWriteBase) -> None:
|
|
14
|
+
"""Import a GEDCOM file into a Gramps database.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
|
|
18
|
+
- file_input: The GEDCOM file to import. This can be a string, Path object, or file-like object.
|
|
19
|
+
- db: The Gramps database to import the GEDCOM file into.
|
|
20
|
+
"""
|
|
21
|
+
# Check if file_input is a string or Path object
|
|
22
|
+
if isinstance(file_input, (str, Path)):
|
|
23
|
+
with open(file_input, "r", encoding="utf-8") as f:
|
|
24
|
+
gedcom_data: str = f.read()
|
|
25
|
+
elif isinstance(file_input, TextIO):
|
|
26
|
+
gedcom_data = file_input.read()
|
|
27
|
+
elif isinstance(file_input, BinaryIO):
|
|
28
|
+
gedcom_data = file_input.read().decode("utf-8")
|
|
29
|
+
else:
|
|
30
|
+
raise TypeError(
|
|
31
|
+
"file_input must be a string, Path object, or file-like object."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
gedcom_structures = gedcom7.loads(gedcom_data)
|
|
35
|
+
process.process_gedcom_structures(gedcom_structures, db)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Handle GEDCOM individual records and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from gedcom7 import const as g7const
|
|
6
|
+
from gedcom7 import grammar as g7grammar
|
|
7
|
+
from gedcom7 import types as g7types
|
|
8
|
+
from gramps.gen.lib import (
|
|
9
|
+
EventRef,
|
|
10
|
+
EventType,
|
|
11
|
+
MediaRef,
|
|
12
|
+
Name,
|
|
13
|
+
NameType,
|
|
14
|
+
Person,
|
|
15
|
+
Surname,
|
|
16
|
+
)
|
|
17
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
18
|
+
|
|
19
|
+
from . import util
|
|
20
|
+
from .event import handle_event
|
|
21
|
+
|
|
22
|
+
GENDER_MAP = {
|
|
23
|
+
"M": Person.MALE,
|
|
24
|
+
"F": Person.FEMALE,
|
|
25
|
+
"U": Person.UNKNOWN,
|
|
26
|
+
"X": Person.OTHER,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
NAME_TYPE_MAP = {
|
|
30
|
+
"AKA": NameType(NameType.AKA),
|
|
31
|
+
"BIRTH": NameType(NameType.BIRTH),
|
|
32
|
+
"MARR": NameType(NameType.MARRIED),
|
|
33
|
+
"OTHER": NameType(NameType.CUSTOM),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
EVENT_TYPE_MAP = {
|
|
37
|
+
g7const.ADOP: EventType.ADOPT,
|
|
38
|
+
g7const.BAPM: EventType.BAPTISM,
|
|
39
|
+
g7const.BARM: EventType.BAR_MITZVAH,
|
|
40
|
+
g7const.BASM: EventType.BAS_MITZVAH,
|
|
41
|
+
g7const.BIRT: EventType.BIRTH,
|
|
42
|
+
g7const.BLES: EventType.BLESS,
|
|
43
|
+
g7const.BURI: EventType.BURIAL,
|
|
44
|
+
g7const.CENS: EventType.CENSUS,
|
|
45
|
+
g7const.CHR: EventType.CHRISTEN,
|
|
46
|
+
g7const.CHRA: EventType.ADULT_CHRISTEN,
|
|
47
|
+
g7const.CONF: EventType.CONFIRMATION,
|
|
48
|
+
g7const.CREM: EventType.CREMATION,
|
|
49
|
+
g7const.DEAT: EventType.DEATH,
|
|
50
|
+
g7const.EMIG: EventType.EMIGRATION,
|
|
51
|
+
g7const.FCOM: EventType.FIRST_COMMUN,
|
|
52
|
+
g7const.GRAD: EventType.GRADUATION,
|
|
53
|
+
g7const.IMMI: EventType.IMMIGRATION,
|
|
54
|
+
g7const.NATU: EventType.NATURALIZATION,
|
|
55
|
+
g7const.ORDN: EventType.ORDINATION,
|
|
56
|
+
g7const.PROB: EventType.PROBATE,
|
|
57
|
+
g7const.RETI: EventType.RETIREMENT,
|
|
58
|
+
g7const.WILL: EventType.WILL,
|
|
59
|
+
g7const.EVEN: EventType.CUSTOM,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def handle_individual(
|
|
64
|
+
structure: g7types.GedcomStructure, xref_handle_map: dict[str, str]
|
|
65
|
+
) -> List[BasicPrimaryObject]:
|
|
66
|
+
"""Handle an individual record and convert it to Gramps objects.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
structure: The GEDCOM note structure to handle.
|
|
70
|
+
xref_handle_map: A map of XREFs to Gramps handles.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
A list of Gramps objects created from the GEDCOM structure.
|
|
74
|
+
"""
|
|
75
|
+
person = Person()
|
|
76
|
+
objects = []
|
|
77
|
+
for child in structure.children:
|
|
78
|
+
# TODO handle RESN
|
|
79
|
+
if child.tag == g7const.SEX:
|
|
80
|
+
assert isinstance(child.value, str), "Expected SEX to be a string"
|
|
81
|
+
try:
|
|
82
|
+
gender = GENDER_MAP[child.value]
|
|
83
|
+
person.set_gender(gender)
|
|
84
|
+
except KeyError:
|
|
85
|
+
raise ValueError(f"SEX value '{child.value}' is not valid")
|
|
86
|
+
elif child.tag == g7const.NAME:
|
|
87
|
+
name, other_objects = handle_name(child, xref_handle_map=xref_handle_map)
|
|
88
|
+
objects.extend(other_objects)
|
|
89
|
+
if person.primary_name.is_empty():
|
|
90
|
+
person.set_primary_name(name)
|
|
91
|
+
else:
|
|
92
|
+
person.add_alternate_name(name)
|
|
93
|
+
# TODO handle attributes
|
|
94
|
+
# TODO handle SUBM
|
|
95
|
+
# TODO handle associations
|
|
96
|
+
# TODO handle ALIA
|
|
97
|
+
# TODO handle ANCI
|
|
98
|
+
# TODO handle DESI
|
|
99
|
+
# TODO handle source citations
|
|
100
|
+
# TODO handle indentifier
|
|
101
|
+
elif child.tag == g7const.FAMC and child.pointer != g7grammar.voidptr:
|
|
102
|
+
family_handle = xref_handle_map.get(child.pointer)
|
|
103
|
+
if not family_handle:
|
|
104
|
+
raise ValueError(f"Family {child.pointer} not found")
|
|
105
|
+
person.add_parent_family_handle(family_handle)
|
|
106
|
+
# TODO child ref type should be handled in the family!
|
|
107
|
+
# TODO handle FAMC PHRASE
|
|
108
|
+
elif child.tag == g7const.FAMS and child.pointer != g7grammar.voidptr:
|
|
109
|
+
family_handle = xref_handle_map.get(child.pointer)
|
|
110
|
+
if not family_handle:
|
|
111
|
+
raise ValueError(f"Family {child.pointer} not found")
|
|
112
|
+
person.add_family_handle(family_handle)
|
|
113
|
+
# TODO handle FAMS PHRASE
|
|
114
|
+
elif child.tag == g7const.SNOTE and child.pointer != g7grammar.voidptr:
|
|
115
|
+
try:
|
|
116
|
+
note_handle = xref_handle_map[child.pointer]
|
|
117
|
+
except KeyError:
|
|
118
|
+
raise ValueError(f"Shared note {child.pointer} not found")
|
|
119
|
+
person.add_note(note_handle)
|
|
120
|
+
elif child.tag == g7const.NOTE:
|
|
121
|
+
person, note = util.add_note_to_object(child, person)
|
|
122
|
+
objects.append(note)
|
|
123
|
+
elif child.tag == g7const.OBJE:
|
|
124
|
+
media_ref = MediaRef()
|
|
125
|
+
if child.pointer != g7grammar.voidptr:
|
|
126
|
+
media_handle = xref_handle_map.get(child.pointer)
|
|
127
|
+
if not media_handle:
|
|
128
|
+
raise ValueError(f"Multimedia object {child.pointer} not found")
|
|
129
|
+
media_ref.ref = media_handle
|
|
130
|
+
# TODO crop is not implement yet
|
|
131
|
+
# one complication is that GEDCOM uses pixels, Gramps uses fractions.
|
|
132
|
+
# Consequently, image dimensions need to be known to convert.
|
|
133
|
+
person.add_media_reference(media_ref)
|
|
134
|
+
elif child.tag in EVENT_TYPE_MAP:
|
|
135
|
+
event, other_objects = handle_event(
|
|
136
|
+
child, xref_handle_map=xref_handle_map, event_type_map=EVENT_TYPE_MAP
|
|
137
|
+
)
|
|
138
|
+
objects.extend(other_objects)
|
|
139
|
+
event_ref = EventRef()
|
|
140
|
+
event_ref.ref = event.handle
|
|
141
|
+
person.add_event_ref(event_ref)
|
|
142
|
+
objects.append(event)
|
|
143
|
+
person = util.add_ids(person, structure=structure, xref_handle_map=xref_handle_map)
|
|
144
|
+
util.set_change_date(structure=structure, obj=person)
|
|
145
|
+
objects.append(person)
|
|
146
|
+
return objects
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def handle_name(
|
|
150
|
+
structure: g7types.GedcomStructure, xref_handle_map: dict[str, str]
|
|
151
|
+
) -> tuple[Name, list[BasicPrimaryObject]]:
|
|
152
|
+
"""Convert a GEDCOM structure to a Gramps Name object.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
structure: The GEDCOM structure containing the name data.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
A tuple containing the Gramps Name object and a list of additional objects created.
|
|
159
|
+
"""
|
|
160
|
+
name = Name()
|
|
161
|
+
surname = Surname()
|
|
162
|
+
objects = []
|
|
163
|
+
personal_name = structure.value
|
|
164
|
+
assert isinstance(
|
|
165
|
+
personal_name, g7types.PersonalName
|
|
166
|
+
), "Expected structure value to be a PersonalName"
|
|
167
|
+
for child in structure.children:
|
|
168
|
+
if child.tag == g7const.TYPE:
|
|
169
|
+
assert isinstance(child.value, str), "Expected TYPE value to be a string"
|
|
170
|
+
gramps_name_type = NAME_TYPE_MAP.get(child.value, NameType.CUSTOM)
|
|
171
|
+
if gramps_name_type == NameType.CUSTOM:
|
|
172
|
+
pass # TODO handle custom name types
|
|
173
|
+
name.set_type(NameType(gramps_name_type))
|
|
174
|
+
elif child.tag == g7const.NPFX:
|
|
175
|
+
assert isinstance(child.value, str), "Expected NPFX value to be a string"
|
|
176
|
+
name.set_title(child.value)
|
|
177
|
+
elif child.tag == g7const.GIVN:
|
|
178
|
+
assert isinstance(child.value, str), "Expected GIVN value to be a string"
|
|
179
|
+
name.set_first_name(child.value)
|
|
180
|
+
elif child.tag == g7const.NICK:
|
|
181
|
+
assert isinstance(child.value, str), "Expected NICK value to be a string"
|
|
182
|
+
name.set_nick_name(child.value)
|
|
183
|
+
elif child.tag == g7const.SPFX:
|
|
184
|
+
assert isinstance(child.value, str), "Expected SPFX value to be a string"
|
|
185
|
+
surname.set_prefix(child.value)
|
|
186
|
+
elif child.tag == g7const.SURN:
|
|
187
|
+
assert isinstance(child.value, str), "Expected SURN value to be a string"
|
|
188
|
+
surname.set_surname(child.value)
|
|
189
|
+
elif child.tag == g7const.NSFX:
|
|
190
|
+
assert isinstance(child.value, str), "Expected NSFX value to be a string"
|
|
191
|
+
name.set_suffix(child.value)
|
|
192
|
+
elif child.tag == g7const.SNOTE and child.pointer != g7grammar.voidptr:
|
|
193
|
+
try:
|
|
194
|
+
note_handle = xref_handle_map[child.pointer]
|
|
195
|
+
except KeyError:
|
|
196
|
+
raise ValueError(f"Shared note {child.pointer} not found")
|
|
197
|
+
name.add_note(note_handle)
|
|
198
|
+
elif child.tag == g7const.NOTE:
|
|
199
|
+
name, note = util.add_note_to_object(child, name)
|
|
200
|
+
objects.append(note)
|
|
201
|
+
# TODO handle source citation
|
|
202
|
+
# if name parts are not given, use the personal name parts
|
|
203
|
+
if not name.first_name and personal_name.given:
|
|
204
|
+
name.set_first_name(personal_name.given)
|
|
205
|
+
if not surname.surname and personal_name.surname:
|
|
206
|
+
surname.set_surname(personal_name.surname)
|
|
207
|
+
if not name.suffix and personal_name.suffix:
|
|
208
|
+
name.set_suffix(personal_name.suffix)
|
|
209
|
+
if not surname.is_empty():
|
|
210
|
+
name.add_surname(surname)
|
|
211
|
+
# last resort - only single string, no surname markers - take as first name only
|
|
212
|
+
if name.is_empty():
|
|
213
|
+
name.set_first_name(personal_name.fullname)
|
|
214
|
+
return name, objects
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Handle GEDCOM multimedia records and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from gedcom7 import const as g7const
|
|
6
|
+
from gedcom7 import types as g7types
|
|
7
|
+
from gedcom7 import util as g7util
|
|
8
|
+
from gramps.gen.lib import Media
|
|
9
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
10
|
+
|
|
11
|
+
from . import util
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def handle_multimedia(
|
|
15
|
+
structure: g7types.GedcomStructure, xref_handle_map: dict[str, str]
|
|
16
|
+
) -> List[BasicPrimaryObject]:
|
|
17
|
+
"""Handle a multimedia record and convert it to Gramps objects.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
structure: The GEDCOM note structure to handle.
|
|
21
|
+
xref_handle_map: A map of XREFs to Gramps handles.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
A list of Gramps objects created from the GEDCOM structure.
|
|
25
|
+
"""
|
|
26
|
+
media = Media()
|
|
27
|
+
objects = []
|
|
28
|
+
for child in structure.children:
|
|
29
|
+
if child.tag == g7const.SNOTE:
|
|
30
|
+
try:
|
|
31
|
+
note_handle = xref_handle_map[child.pointer]
|
|
32
|
+
except KeyError:
|
|
33
|
+
raise ValueError(f"Shared note {child.pointer} not found")
|
|
34
|
+
media.add_note(note_handle)
|
|
35
|
+
elif child.tag == g7const.NOTE:
|
|
36
|
+
media, note = util.add_note_to_object(child, media)
|
|
37
|
+
objects.append(note)
|
|
38
|
+
# TODO handle RESN
|
|
39
|
+
# TODO handle multiple files
|
|
40
|
+
file_structure = g7util.get_first_child_with_tag(structure, g7const.FILE)
|
|
41
|
+
assert file_structure is not None, "Multimedia structure must have a FILE tag"
|
|
42
|
+
assert isinstance(file_structure.value, str), "Expected FILE value to be a string"
|
|
43
|
+
media.set_path(file_structure.value.removeprefix("file://"))
|
|
44
|
+
form_structure = g7util.get_first_child_with_tag(file_structure, g7const.FORM)
|
|
45
|
+
assert form_structure is not None, "Multimedia file must have a FORM tag"
|
|
46
|
+
assert isinstance(
|
|
47
|
+
form_structure.value, g7types.MediaType
|
|
48
|
+
), "Expected FORM value to be a MediaType"
|
|
49
|
+
media.set_mime_type(form_structure.value.media_type)
|
|
50
|
+
media = util.add_ids(media, structure=structure, xref_handle_map=xref_handle_map)
|
|
51
|
+
util.set_change_date(structure=structure, obj=media)
|
|
52
|
+
objects.append(media)
|
|
53
|
+
return objects
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Handle GEDCOM shared note records and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
5
|
+
from gedcom7 import types as g7types
|
|
6
|
+
from . import util
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handle_shared_note(
|
|
10
|
+
structure: g7types.GedcomStructure, xref_handle_map: dict[str, str]
|
|
11
|
+
) -> List[BasicPrimaryObject]:
|
|
12
|
+
"""Handle a shared note record and convert it to Gramps objects.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
structure: The GEDCOM note structure to handle.
|
|
16
|
+
xref_handle_map: A map of XREFs to Gramps handles.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
A list of Gramps objects created from the GEDCOM structure.
|
|
20
|
+
"""
|
|
21
|
+
note = util.structure_to_note(structure)
|
|
22
|
+
# set note handle and Gramps ID
|
|
23
|
+
note = util.add_ids(note, structure=structure, xref_handle_map=xref_handle_map)
|
|
24
|
+
# set last change timestamp
|
|
25
|
+
util.set_change_date(structure=structure, obj=note)
|
|
26
|
+
return [note]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Process GEDCOM structures and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from gedcom7 import const as g7const
|
|
6
|
+
from gedcom7 import types as g7types
|
|
7
|
+
from gramps.gen.db import DbTxn, DbWriteBase
|
|
8
|
+
|
|
9
|
+
from .family import handle_family
|
|
10
|
+
from .header import handle_header
|
|
11
|
+
from .individual import handle_individual
|
|
12
|
+
from .multimedia import handle_multimedia
|
|
13
|
+
from .note import handle_shared_note
|
|
14
|
+
from .repository import handle_repository
|
|
15
|
+
from .source import handle_source
|
|
16
|
+
from .submitter import handle_submitter
|
|
17
|
+
from .util import make_handle
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def process_gedcom_structures(
|
|
21
|
+
gedcom_structures: list[g7types.GedcomStructure], db: DbWriteBase
|
|
22
|
+
):
|
|
23
|
+
"""Process GEDCOM structures and import them into the Gramps database.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
gedcom_structures: The GEDCOM structures to process.
|
|
27
|
+
db: The Gramps database to import the GEDCOM structures into.
|
|
28
|
+
"""
|
|
29
|
+
if len(gedcom_structures) < 2:
|
|
30
|
+
raise ValueError("No GEDCOM structures to process.")
|
|
31
|
+
first_structure = gedcom_structures[0]
|
|
32
|
+
if first_structure.tag != g7const.HEAD:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"First structure must be a HEAD structure, but got {first_structure.tag}"
|
|
35
|
+
)
|
|
36
|
+
last_structure = gedcom_structures[-1]
|
|
37
|
+
if last_structure.tag != g7const.TRLR:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"Last structure must be a TRLR structure, but got {last_structure.tag}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
handle_header(first_structure, db)
|
|
43
|
+
|
|
44
|
+
# Create a map of handles to XREFs
|
|
45
|
+
xref_handle_map = {}
|
|
46
|
+
for structure in gedcom_structures:
|
|
47
|
+
if structure.xref and structure.xref not in xref_handle_map:
|
|
48
|
+
xref_handle_map[structure.xref] = make_handle()
|
|
49
|
+
|
|
50
|
+
# Handle the remaining structures (excluding header and trailer)
|
|
51
|
+
objects = []
|
|
52
|
+
for structure in gedcom_structures[1:-1]:
|
|
53
|
+
objects += handle_structure(structure, xref_handle_map=xref_handle_map) or []
|
|
54
|
+
|
|
55
|
+
add_objects_to_database(objects, db)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def handle_structure(
|
|
59
|
+
structure: g7types.GedcomStructure, xref_handle_map: dict[str, str]
|
|
60
|
+
) -> list | None:
|
|
61
|
+
"""Handle a single GEDCOM structure and import it into the Gramps database.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
structure: The GEDCOM structure to handle.
|
|
65
|
+
db: The Gramps database to import the GEDCOM structure into.
|
|
66
|
+
"""
|
|
67
|
+
if structure.tag == g7const.FAM:
|
|
68
|
+
return handle_family(structure, xref_handle_map=xref_handle_map)
|
|
69
|
+
elif structure.tag == g7const.INDI:
|
|
70
|
+
return handle_individual(structure, xref_handle_map=xref_handle_map)
|
|
71
|
+
elif structure.tag == g7const.OBJE:
|
|
72
|
+
return handle_multimedia(structure, xref_handle_map=xref_handle_map)
|
|
73
|
+
elif structure.tag == g7const.REPO:
|
|
74
|
+
return handle_repository(structure, xref_handle_map=xref_handle_map)
|
|
75
|
+
elif structure.tag == g7const.SNOTE:
|
|
76
|
+
return handle_shared_note(structure, xref_handle_map=xref_handle_map)
|
|
77
|
+
elif structure.tag == g7const.SOUR:
|
|
78
|
+
return handle_source(structure, xref_handle_map=xref_handle_map)
|
|
79
|
+
elif structure.tag == g7const.SUBM:
|
|
80
|
+
return handle_submitter(structure)
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def add_objects_to_database(objects, db):
|
|
85
|
+
with DbTxn("Add child to family", db) as transaction:
|
|
86
|
+
for obj in objects:
|
|
87
|
+
if obj.__class__.__name__ == "Person":
|
|
88
|
+
db.add_person(obj, transaction)
|
|
89
|
+
elif obj.__class__.__name__ == "Family":
|
|
90
|
+
db.add_family(obj, transaction)
|
|
91
|
+
elif obj.__class__.__name__ == "Event":
|
|
92
|
+
db.add_event(obj, transaction)
|
|
93
|
+
elif obj.__class__.__name__ == "Citation":
|
|
94
|
+
db.add_citation(obj, transaction)
|
|
95
|
+
elif obj.__class__.__name__ == "Source":
|
|
96
|
+
db.add_source(obj, transaction)
|
|
97
|
+
elif obj.__class__.__name__ == "Note":
|
|
98
|
+
db.add_note(obj, transaction)
|
|
99
|
+
elif obj.__class__.__name__ == "Media":
|
|
100
|
+
db.add_media(obj, transaction)
|
|
101
|
+
elif obj.__class__.__name__ == "Place":
|
|
102
|
+
db.add_place(obj, transaction)
|
|
103
|
+
elif obj.__class__.__name__ == "Repository":
|
|
104
|
+
db.add_repository(obj, transaction)
|
|
105
|
+
elif obj.__class__.__name__ == "Tag":
|
|
106
|
+
db.add_tag(obj, transaction)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Handle GEDCOM repository records and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
from gramps.gen.lib import Repository, NoteType, Url, UrlType
|
|
5
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
6
|
+
from gedcom7 import types as g7types, const as g7const, util as g7util
|
|
7
|
+
from . import util
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def handle_repository(
|
|
11
|
+
structure: g7types.GedcomStructure, xref_handle_map: dict[str, str]
|
|
12
|
+
) -> List[BasicPrimaryObject]:
|
|
13
|
+
"""Handle a repository record and convert it to Gramps objects.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
structure: The GEDCOM note structure to handle.
|
|
17
|
+
xref_handle_map: A map of XREFs to Gramps handles.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
A list of Gramps objects created from the GEDCOM structure.
|
|
21
|
+
"""
|
|
22
|
+
repository = Repository()
|
|
23
|
+
objects = []
|
|
24
|
+
for child in structure.children:
|
|
25
|
+
if child.tag == g7const.NAME:
|
|
26
|
+
# set repository name
|
|
27
|
+
if child.value is not None:
|
|
28
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
29
|
+
repository.set_name(child.value)
|
|
30
|
+
elif child.tag == g7const.SNOTE:
|
|
31
|
+
try:
|
|
32
|
+
note_handle = xref_handle_map[child.pointer]
|
|
33
|
+
except KeyError:
|
|
34
|
+
raise ValueError(f"Shared note {child.pointer} not found")
|
|
35
|
+
repository.add_note(note_handle)
|
|
36
|
+
elif child.tag == g7const.WWW:
|
|
37
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
38
|
+
url = Url()
|
|
39
|
+
url.set_path(child.value)
|
|
40
|
+
url.set_type(UrlType(UrlType.WEB_HOME))
|
|
41
|
+
repository.add_url(url)
|
|
42
|
+
elif child.tag == g7const.EMAIL:
|
|
43
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
44
|
+
url = Url()
|
|
45
|
+
url.set_path(child.value)
|
|
46
|
+
url.set_type(UrlType(UrlType.EMAIL))
|
|
47
|
+
repository.add_url(url)
|
|
48
|
+
elif child.tag == g7const.NOTE:
|
|
49
|
+
repository, note = util.add_note_to_object(child, repository)
|
|
50
|
+
objects.append(note)
|
|
51
|
+
repository = util.add_ids(
|
|
52
|
+
repository, structure=structure, xref_handle_map=xref_handle_map
|
|
53
|
+
)
|
|
54
|
+
util.set_change_date(structure=structure, obj=repository)
|
|
55
|
+
objects.append(repository)
|
|
56
|
+
return objects
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Handle GEDCOM source records and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from gedcom7 import const as g7const
|
|
6
|
+
from gedcom7 import types as g7types
|
|
7
|
+
from gedcom7 import util as g7util
|
|
8
|
+
from gramps.gen.lib import MediaRef, Note, NoteType, RepoRef, Source, SourceMediaType
|
|
9
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
10
|
+
|
|
11
|
+
from . import util
|
|
12
|
+
|
|
13
|
+
MEDIA_TYPE_MAP = {
|
|
14
|
+
"AUDIO": SourceMediaType.AUDIO,
|
|
15
|
+
"BOOK": SourceMediaType.BOOK,
|
|
16
|
+
"CARD": SourceMediaType.CARD,
|
|
17
|
+
"ELECTRONIC": SourceMediaType.ELECTRONIC,
|
|
18
|
+
"FICHE": SourceMediaType.FICHE,
|
|
19
|
+
"FILM": SourceMediaType.FILM,
|
|
20
|
+
"MAGAZINE": SourceMediaType.MAGAZINE,
|
|
21
|
+
"MANUSCRIPT": SourceMediaType.MANUSCRIPT,
|
|
22
|
+
"MAP": SourceMediaType.MAP,
|
|
23
|
+
"NEWSPAPER": SourceMediaType.NEWSPAPER,
|
|
24
|
+
"PHOTO": SourceMediaType.PHOTO,
|
|
25
|
+
"TOMBSTONE": SourceMediaType.TOMBSTONE,
|
|
26
|
+
"VIDEO": SourceMediaType.VIDEO,
|
|
27
|
+
"OTHER": SourceMediaType.CUSTOM,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def handle_source(
|
|
32
|
+
structure: g7types.GedcomStructure, xref_handle_map: dict[str, str]
|
|
33
|
+
) -> List[BasicPrimaryObject]:
|
|
34
|
+
"""Handle a source record and convert it to Gramps objects.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
structure: The GEDCOM note structure to handle.
|
|
38
|
+
xref_handle_map: A map of XREFs to Gramps handles.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
A list of Gramps objects created from the GEDCOM structure.
|
|
42
|
+
"""
|
|
43
|
+
source = Source()
|
|
44
|
+
objects = []
|
|
45
|
+
for child in structure.children:
|
|
46
|
+
if child.tag == g7const.TITL:
|
|
47
|
+
# set source title
|
|
48
|
+
if child.value is not None:
|
|
49
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
50
|
+
source.set_title(child.value)
|
|
51
|
+
elif child.tag == g7const.AUTH:
|
|
52
|
+
# set source author
|
|
53
|
+
if child.value is not None:
|
|
54
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
55
|
+
source.set_author(child.value)
|
|
56
|
+
elif child.tag == g7const.PUBL:
|
|
57
|
+
# set source publisher
|
|
58
|
+
if child.value is not None:
|
|
59
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
60
|
+
source.set_publication_info(child.value)
|
|
61
|
+
elif child.tag == g7const.ABBR:
|
|
62
|
+
# set source abbreviation
|
|
63
|
+
if child.value is not None:
|
|
64
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
65
|
+
source.set_abbreviation(child.value)
|
|
66
|
+
# add the note text as a source note
|
|
67
|
+
elif child.tag == g7const.TEXT:
|
|
68
|
+
note = Note()
|
|
69
|
+
note.type = NoteType(NoteType.SOURCE_TEXT)
|
|
70
|
+
if child.value is not None:
|
|
71
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
72
|
+
note.set(child.value)
|
|
73
|
+
note.handle = util.make_handle()
|
|
74
|
+
source.add_note(note.handle)
|
|
75
|
+
elif child.tag == g7const.REPO:
|
|
76
|
+
repo_ref = RepoRef()
|
|
77
|
+
try:
|
|
78
|
+
repo_handle = xref_handle_map[child.pointer]
|
|
79
|
+
except KeyError:
|
|
80
|
+
raise ValueError(f"Repository {child.pointer} not found")
|
|
81
|
+
repo_ref.ref = repo_handle
|
|
82
|
+
call_number = g7util.get_first_child_with_tag(child, g7const.CALN)
|
|
83
|
+
if call_number:
|
|
84
|
+
repo_ref.set_call_number(call_number.value)
|
|
85
|
+
media_type = g7util.get_first_child_with_tag(call_number, g7const.MEDI)
|
|
86
|
+
if media_type:
|
|
87
|
+
assert isinstance(
|
|
88
|
+
media_type.value, str
|
|
89
|
+
), "Expected value to be a string"
|
|
90
|
+
gramps_source_media_type = MEDIA_TYPE_MAP.get(
|
|
91
|
+
media_type.value, SourceMediaType.CUSTOM
|
|
92
|
+
)
|
|
93
|
+
repo_ref.set_media_type(SourceMediaType(gramps_source_media_type))
|
|
94
|
+
source.add_repo_reference(repo_ref)
|
|
95
|
+
elif child.tag == g7const.SNOTE:
|
|
96
|
+
try:
|
|
97
|
+
note_handle = xref_handle_map[child.pointer]
|
|
98
|
+
except KeyError:
|
|
99
|
+
raise ValueError(f"Shared note {child.pointer} not found")
|
|
100
|
+
source.add_note(note_handle)
|
|
101
|
+
elif child.tag == g7const.NOTE:
|
|
102
|
+
source, note = util.add_note_to_object(child, source)
|
|
103
|
+
objects.append(note)
|
|
104
|
+
elif child.tag == g7const.OBJE:
|
|
105
|
+
media_ref = MediaRef()
|
|
106
|
+
media_handle = xref_handle_map.get(child.pointer)
|
|
107
|
+
if not media_handle:
|
|
108
|
+
raise ValueError(f"Multimedia object {child.pointer} not found")
|
|
109
|
+
media_ref.ref = media_handle
|
|
110
|
+
# TODO crop is not implement yet
|
|
111
|
+
# one complication is that GEDCOM uses pixels, Gramps uses fractions.
|
|
112
|
+
# Consequently, image dimensions need to be known to convert.
|
|
113
|
+
source.add_media_reference(media_ref)
|
|
114
|
+
source = util.add_ids(source, structure=structure, xref_handle_map=xref_handle_map)
|
|
115
|
+
util.set_change_date(structure=structure, obj=source)
|
|
116
|
+
objects.append(source)
|
|
117
|
+
return objects
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Handle GEDCOM submitter records and import them into the Gramps database."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
5
|
+
from gedcom7 import types as g7types
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handle_submitter(structure: g7types.GedcomStructure) -> List[BasicPrimaryObject]:
|
|
9
|
+
"""Handle a submitter record and convert it to Gramps objects.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
structure: The GEDCOM submitter structure to handle.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
A list of Gramps objects created from the GEDCOM structure.
|
|
16
|
+
"""
|
|
17
|
+
# TODO: Implement submitter import
|
|
18
|
+
return []
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
from gramps.gen.lib.primaryobj import BasicPrimaryObject
|
|
4
|
+
from gramps.gen.lib.notebase import NoteBase
|
|
5
|
+
|
|
6
|
+
BasicPrimaryObjectT = TypeVar("BasicPrimaryObjectT", bound=BasicPrimaryObject)
|
|
7
|
+
NoteBaseT = TypeVar("NoteBaseT", bound=NoteBase)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Utility functions for handling GEDCOM 7 data in Gramps."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from gedcom7 import const as g7const
|
|
8
|
+
from gedcom7 import types as g7types
|
|
9
|
+
from gedcom7 import util as g7util
|
|
10
|
+
from gramps.gen.lib import Date, Note, NoteType
|
|
11
|
+
|
|
12
|
+
from .types import BasicPrimaryObjectT, NoteBaseT
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_handle() -> str:
|
|
16
|
+
"""Generate a unique handle for a new object."""
|
|
17
|
+
return uuid.uuid4().hex
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def add_ids(
|
|
21
|
+
obj: BasicPrimaryObjectT,
|
|
22
|
+
structure: g7types.GedcomStructure,
|
|
23
|
+
xref_handle_map: dict[str, str],
|
|
24
|
+
) -> BasicPrimaryObjectT:
|
|
25
|
+
"""Add a handle and Gramps ID to a new Gramps object."""
|
|
26
|
+
if not structure.xref or len(structure.xref) < 3:
|
|
27
|
+
raise ValueError(f"Invalid xref ID: {structure.xref}")
|
|
28
|
+
if structure.xref not in xref_handle_map:
|
|
29
|
+
raise ValueError(f"Xref ID {structure.xref} not found in xref_handle_map")
|
|
30
|
+
obj.handle = xref_handle_map[structure.xref]
|
|
31
|
+
obj.gramps_id = structure.xref[1:-1]
|
|
32
|
+
return obj
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
GEDCOM_MONTHS = {
|
|
36
|
+
"JAN": 1,
|
|
37
|
+
"FEB": 2,
|
|
38
|
+
"MAR": 3,
|
|
39
|
+
"APR": 4,
|
|
40
|
+
"MAY": 5,
|
|
41
|
+
"JUN": 6,
|
|
42
|
+
"JUL": 7,
|
|
43
|
+
"AUG": 8,
|
|
44
|
+
"SEP": 9,
|
|
45
|
+
"OCT": 10,
|
|
46
|
+
"NOV": 11,
|
|
47
|
+
"DEC": 12,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def set_change_date(
|
|
52
|
+
structure: g7types.GedcomStructure,
|
|
53
|
+
obj: BasicPrimaryObjectT,
|
|
54
|
+
) -> BasicPrimaryObjectT:
|
|
55
|
+
"""Set the change date for a Gramps object."""
|
|
56
|
+
change_structure = g7util.get_first_child_with_tag(structure, g7const.CHAN)
|
|
57
|
+
if not change_structure:
|
|
58
|
+
# take creation date as fallback
|
|
59
|
+
change_structure = g7util.get_first_child_with_tag(structure, g7const.CREA)
|
|
60
|
+
if not change_structure:
|
|
61
|
+
# no date found
|
|
62
|
+
return obj
|
|
63
|
+
date_structure = g7util.get_first_child_with_tag(change_structure, g7const.DATE)
|
|
64
|
+
if not date_structure:
|
|
65
|
+
# no date found
|
|
66
|
+
return obj
|
|
67
|
+
assert isinstance(
|
|
68
|
+
date_structure.value, g7types.DateExact
|
|
69
|
+
), "Expected date to be a DateExact object"
|
|
70
|
+
time_structure = g7util.get_first_child_with_tag(change_structure, g7const.TIME)
|
|
71
|
+
if time_structure:
|
|
72
|
+
assert isinstance(
|
|
73
|
+
time_structure.value, g7types.Time
|
|
74
|
+
), "Expected time to be a Time object"
|
|
75
|
+
time = time_structure.value
|
|
76
|
+
else:
|
|
77
|
+
time = None
|
|
78
|
+
datetime_value = g7util.date_exact_and_time_to_python_datetime(
|
|
79
|
+
date=date_structure.value,
|
|
80
|
+
time=time,
|
|
81
|
+
)
|
|
82
|
+
obj.change = int(datetime_value.timestamp())
|
|
83
|
+
return obj
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def structure_to_note(structure: g7types.GedcomStructure) -> Note:
|
|
87
|
+
"""Create a note from a GEDCOM structure of type NOTE or SNOTE.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
structure: The GEDCOM note structure to handle.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A note object.
|
|
94
|
+
"""
|
|
95
|
+
note = Note()
|
|
96
|
+
if structure.value is not None:
|
|
97
|
+
assert isinstance(structure.value, str), "Expected value to be a string"
|
|
98
|
+
note.set(structure.value)
|
|
99
|
+
for child in structure.children:
|
|
100
|
+
# set note type to HTML if MIME type is HTML
|
|
101
|
+
if child.tag == g7const.MIME:
|
|
102
|
+
if child.value == g7const.MIME_HTML:
|
|
103
|
+
note.type = NoteType(NoteType.HTML_CODE)
|
|
104
|
+
elif child.tag == g7const.TRAN:
|
|
105
|
+
# iterate over translations - we just append them
|
|
106
|
+
if child.value is None:
|
|
107
|
+
continue
|
|
108
|
+
assert isinstance(child.value, str), "Expected value to be a string"
|
|
109
|
+
note.append("\n\n" + child.value)
|
|
110
|
+
return note
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def add_note_to_object(
|
|
114
|
+
structure: g7types.GedcomStructure,
|
|
115
|
+
obj: NoteBaseT,
|
|
116
|
+
) -> tuple[NoteBaseT, Note]:
|
|
117
|
+
"""Add a note to a Gramps object."""
|
|
118
|
+
note = structure_to_note(structure)
|
|
119
|
+
note.type = NoteType(NoteType.SOURCE)
|
|
120
|
+
note.handle = make_handle()
|
|
121
|
+
# set note change date to parent change date
|
|
122
|
+
set_change_date(structure=structure, obj=note)
|
|
123
|
+
obj.add_note(note.handle)
|
|
124
|
+
return obj, note
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_next_gramps_id(
|
|
128
|
+
xref_handle_map: dict[str, str],
|
|
129
|
+
prefix: str,
|
|
130
|
+
) -> str:
|
|
131
|
+
"""Get the next available Gramps ID for a given prefix."""
|
|
132
|
+
existing_ids = {handle[1:-1] for handle in xref_handle_map.values()}
|
|
133
|
+
next_id = 1
|
|
134
|
+
while f"{prefix}{next_id:04d}" in existing_ids:
|
|
135
|
+
next_id += 1
|
|
136
|
+
return f"{prefix}{next_id:04d}"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
CALENDAR_MAP = {
|
|
140
|
+
"GREGORIAN": Date.CAL_GREGORIAN,
|
|
141
|
+
"JULIAN": Date.CAL_JULIAN,
|
|
142
|
+
"HEBREW": Date.CAL_HEBREW,
|
|
143
|
+
"FRENCH_R": Date.CAL_FRENCH,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def gedcom_date_value_to_gramps_date(
|
|
148
|
+
date_value: g7types.DateValue,
|
|
149
|
+
) -> Date:
|
|
150
|
+
"""Convert a GEDCOM date value to a Gramps date."""
|
|
151
|
+
date = Date()
|
|
152
|
+
if isinstance(date_value, g7types.Date):
|
|
153
|
+
year = date_value.year or 0
|
|
154
|
+
month = GEDCOM_MONTHS.get(date_value.month or "", 0)
|
|
155
|
+
day = date_value.day or 0
|
|
156
|
+
date.set_yr_mon_day(year=year, month=month, day=day)
|
|
157
|
+
if date_value.calendar is not None and date_value.calendar in CALENDAR_MAP:
|
|
158
|
+
date.set_calendar(CALENDAR_MAP[date_value.calendar])
|
|
159
|
+
elif isinstance(date_value, g7types.DatePeriod):
|
|
160
|
+
# TODO
|
|
161
|
+
pass
|
|
162
|
+
elif isinstance(date_value, g7types.DateApprox):
|
|
163
|
+
# TODO
|
|
164
|
+
pass
|
|
165
|
+
elif isinstance(date_value, g7types.DateRange):
|
|
166
|
+
# TODO
|
|
167
|
+
pass
|
|
168
|
+
return date
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gramps_gedcom7
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: A GEDCOM 7 support library for Gramps genealogy software
|
|
5
|
+
Author-email: David Straub <straub@protonmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DavidMStraub/gramps-gedcom7
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: gramps>=6.0.0
|
|
12
|
+
Requires-Dist: gedcom7>=0.3.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# gramps-gedcom7
|
|
16
|
+
|
|
17
|
+
A GEDCOM 7 import library for Gramps.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
gramps_gedcom7/__init__.py
|
|
5
|
+
gramps_gedcom7/event.py
|
|
6
|
+
gramps_gedcom7/family.py
|
|
7
|
+
gramps_gedcom7/header.py
|
|
8
|
+
gramps_gedcom7/importer.py
|
|
9
|
+
gramps_gedcom7/individual.py
|
|
10
|
+
gramps_gedcom7/multimedia.py
|
|
11
|
+
gramps_gedcom7/note.py
|
|
12
|
+
gramps_gedcom7/process.py
|
|
13
|
+
gramps_gedcom7/repository.py
|
|
14
|
+
gramps_gedcom7/source.py
|
|
15
|
+
gramps_gedcom7/submitter.py
|
|
16
|
+
gramps_gedcom7/types.py
|
|
17
|
+
gramps_gedcom7/util.py
|
|
18
|
+
gramps_gedcom7.egg-info/PKG-INFO
|
|
19
|
+
gramps_gedcom7.egg-info/SOURCES.txt
|
|
20
|
+
gramps_gedcom7.egg-info/dependency_links.txt
|
|
21
|
+
gramps_gedcom7.egg-info/requires.txt
|
|
22
|
+
gramps_gedcom7.egg-info/top_level.txt
|
|
23
|
+
test/test_importer.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gramps_gedcom7
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gramps_gedcom7"
|
|
7
|
+
version = "0.0.3"
|
|
8
|
+
description = "A GEDCOM 7 support library for Gramps genealogy software"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "David Straub", email = "straub@protonmail.com"},
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"gramps>=6.0.0",
|
|
17
|
+
"gedcom7>=0.3.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
"Homepage" = "https://github.com/DavidMStraub/gramps-gedcom7"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools]
|
|
24
|
+
packages = ["gramps_gedcom7"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from gramps_gedcom7.importer import import_gedcom
|
|
2
|
+
|
|
3
|
+
from gramps.gen.db.utils import make_database
|
|
4
|
+
|
|
5
|
+
def test_importer_maximal70():
|
|
6
|
+
# Test the import_gedcom function with a maximal GEDCOM 7.0 file
|
|
7
|
+
gedcom_file = "test/data/maximal70.ged"
|
|
8
|
+
db = make_database("sqlite")
|
|
9
|
+
db.load(":memory:")
|
|
10
|
+
import_gedcom(gedcom_file, db)
|