gedcom-x 0.5.6__py3-none-any.whl → 0.5.8__py3-none-any.whl
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.
- {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.8.dist-info}/METADATA +1 -1
- gedcom_x-0.5.8.dist-info/RECORD +56 -0
- gedcomx/Extensions/__init__.py +1 -0
- gedcomx/Extensions/rs10/__init__.py +1 -0
- gedcomx/Extensions/rs10/rsLink.py +116 -0
- gedcomx/TopLevelTypeCollection.py +1 -1
- gedcomx/__init__.py +43 -41
- gedcomx/{Address.py → address.py} +13 -13
- gedcomx/{Agent.py → agent.py} +52 -24
- gedcomx/{Attribution.py → attribution.py} +36 -9
- gedcomx/{Conclusion.py → conclusion.py} +49 -21
- gedcomx/converter.py +1049 -0
- gedcomx/coverage.py +55 -0
- gedcomx/{Date.py → date.py} +11 -4
- gedcomx/{Document.py → document.py} +27 -8
- gedcomx/{Event.py → event.py} +102 -27
- gedcomx/{EvidenceReference.py → evidence_reference.py} +2 -2
- gedcomx/{Fact.py → fact.py} +45 -34
- gedcomx/{Gedcom5x.py → gedcom5x.py} +78 -61
- gedcomx/gedcom7/Exceptions.py +9 -0
- gedcomx/gedcom7/Gedcom7.py +160 -0
- gedcomx/gedcom7/GedcomStructure.py +94 -0
- gedcomx/gedcom7/Specification.py +347 -0
- gedcomx/gedcom7/__init__.py +26 -0
- gedcomx/gedcom7/g7interop.py +205 -0
- gedcomx/gedcom7/logger.py +19 -0
- gedcomx/gedcomx.py +501 -0
- gedcomx/{Gender.py → gender.py} +29 -17
- gedcomx/group.py +63 -0
- gedcomx/{Identifier.py → identifier.py} +13 -16
- gedcomx/{LoggingHub.py → logging_hub.py} +21 -0
- gedcomx/{Mutations.py → mutations.py} +50 -26
- gedcomx/name.py +396 -0
- gedcomx/{Note.py → note.py} +17 -10
- gedcomx/{OnlineAccount.py → online_account.py} +1 -1
- gedcomx/{Person.py → person.py} +52 -29
- gedcomx/place_description.py +123 -0
- gedcomx/place_reference.py +62 -0
- gedcomx/qualifier.py +54 -0
- gedcomx/{Relationship.py → relationship.py} +33 -13
- gedcomx/resource.py +85 -0
- gedcomx/serialization.py +815 -0
- gedcomx/{SourceDescription.py → source_description.py} +144 -85
- gedcomx/{SourceReference.py → source_reference.py} +15 -14
- gedcomx/{Subject.py → subject.py} +30 -28
- gedcomx/{GedcomX.py → translation.py} +283 -446
- gedcomx/{URI.py → uri.py} +42 -26
- gedcom_x-0.5.6.dist-info/RECORD +0 -45
- gedcomx/Coverage.py +0 -36
- gedcomx/Group.py +0 -37
- gedcomx/Name.py +0 -276
- gedcomx/PlaceDescription.py +0 -70
- gedcomx/PlaceReference.py +0 -30
- gedcomx/Qualifier.py +0 -27
- gedcomx/Resource.py +0 -75
- gedcomx/Serialization.py +0 -401
- gedcomx/Translation.py +0 -219
- {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.8.dist-info}/WHEEL +0 -0
- {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.8.dist-info}/top_level.txt +0 -0
- /gedcomx/{Exceptions.py → exceptions.py} +0 -0
- /gedcomx/{ExtensibleEnum.py → extensible_enum.py} +0 -0
- /gedcomx/{Gedcom.py → gedcom.py} +0 -0
- /gedcomx/{SourceCitation.py → source_citation.py} +0 -0
- /gedcomx/{TextValue.py → textvalue.py} +0 -0
@@ -5,9 +5,9 @@ from typing import List, Optional, Dict, Any
|
|
5
5
|
|
6
6
|
from collections.abc import Iterator
|
7
7
|
import json
|
8
|
-
from .
|
9
|
-
from .
|
10
|
-
from .
|
8
|
+
from .resource import Resource
|
9
|
+
from .uri import URI
|
10
|
+
from .extensible_enum import ExtensibleEnum
|
11
11
|
|
12
12
|
import secrets
|
13
13
|
import string
|
@@ -26,11 +26,12 @@ def make_uid(length: int = 10, alphabet: str = string.ascii_letters + string.dig
|
|
26
26
|
"""
|
27
27
|
if length <= 0:
|
28
28
|
raise ValueError("length must be > 0")
|
29
|
-
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
29
|
+
return ''.join(secrets.choice(alphabet) for _ in range(length)).upper()
|
30
30
|
|
31
31
|
class IdentifierType(ExtensibleEnum):
|
32
|
-
"""Enumeration of identifier types."""
|
33
32
|
pass
|
33
|
+
|
34
|
+
"""Enumeration of identifier types."""
|
34
35
|
IdentifierType.register("Primary", "http://gedcomx.org/Primary")
|
35
36
|
IdentifierType.register("Authority", "http://gedcomx.org/Authority")
|
36
37
|
IdentifierType.register("Deprecated", "http://gedcomx.org/Deprecated")
|
@@ -42,7 +43,7 @@ class Identifier:
|
|
42
43
|
identifier = 'http://gedcomx.org/v1/Identifier'
|
43
44
|
version = 'http://gedcomx.org/conceptual-model/v1'
|
44
45
|
|
45
|
-
def __init__(self, value: Optional[List[URI]], type: Optional[IdentifierType] = IdentifierType.Primary) -> None:
|
46
|
+
def __init__(self, value: Optional[List[URI]], type: Optional[IdentifierType] = IdentifierType.Primary) -> None: # type: ignore
|
46
47
|
if not isinstance(value,list):
|
47
48
|
value = [value] if value else []
|
48
49
|
self.type = type
|
@@ -50,12 +51,12 @@ class Identifier:
|
|
50
51
|
|
51
52
|
@property
|
52
53
|
def _as_dict_(self):
|
53
|
-
from .
|
54
|
-
type_as_dict = {
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
from .serialization import Serialization
|
55
|
+
type_as_dict = {}
|
56
|
+
if self.values:
|
57
|
+
type_as_dict["value"] = list(self.values) # or [v for v in self.values]
|
58
|
+
if self.type:
|
59
|
+
type_as_dict["type"] = getattr(self.type, "value", self.type) # type: ignore[attr-defined]
|
59
60
|
|
60
61
|
return Serialization.serialize_dict(type_as_dict)
|
61
62
|
|
@@ -64,10 +65,6 @@ class Identifier:
|
|
64
65
|
"""
|
65
66
|
Construct an Identifier from a dict parsed from JSON.
|
66
67
|
"""
|
67
|
-
#for name, member in IdentifierType.__members__.items():
|
68
|
-
# print(name)
|
69
|
-
|
70
|
-
|
71
68
|
|
72
69
|
for key in data.keys():
|
73
70
|
type = key
|
@@ -2,6 +2,7 @@
|
|
2
2
|
from __future__ import annotations
|
3
3
|
import logging
|
4
4
|
import contextvars
|
5
|
+
import os
|
5
6
|
from contextlib import contextmanager
|
6
7
|
from dataclasses import dataclass
|
7
8
|
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
@@ -184,3 +185,23 @@ class LoggingHub:
|
|
184
185
|
# -------- Utilities --------
|
185
186
|
def set_default_channel(self, name: str) -> None:
|
186
187
|
self._dispatch.set_default_channel(name)
|
188
|
+
|
189
|
+
hub = LoggingHub("gedcomx") # this becomes your app logger root
|
190
|
+
hub.init_root() # do this ONCE at startup
|
191
|
+
|
192
|
+
os.makedirs("logs", exist_ok=True)
|
193
|
+
|
194
|
+
# 1) Start a default channel (file w/ daily rotation)
|
195
|
+
hub.start_channel(
|
196
|
+
ChannelConfig(
|
197
|
+
name="default",
|
198
|
+
path="logs/app.log",
|
199
|
+
level=logging.INFO,
|
200
|
+
fmt="[%(asctime)s] %(levelname)s %(log_channel)s %(name)s: %(message)s",
|
201
|
+
rotation="time:midnight:7",
|
202
|
+
),
|
203
|
+
make_current=True
|
204
|
+
)
|
205
|
+
|
206
|
+
# (optional) Also a console channel you can switch to
|
207
|
+
hub.start_channel(ChannelConfig(name="console", path=None, level=logging.DEBUG))
|
@@ -1,6 +1,29 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
|
2
|
+
"""
|
3
|
+
======================================================================
|
4
|
+
Project: Gedcom-X
|
5
|
+
File: mutations.py
|
6
|
+
Author: David J. Cartwright
|
7
|
+
Purpose: Objects used to convert TAGs/Structues/Types from GEDCOM Versions
|
8
|
+
when simple parsing will not work. (complex or ambiguous structures)
|
9
|
+
|
10
|
+
Created: 2025-08-25
|
11
|
+
Updated:
|
12
|
+
- 2025-08-31: cleaned up imports and documentation
|
13
|
+
- 2025-09-01: filename PEP8 standard, imports changed accordingly
|
14
|
+
|
15
|
+
======================================================================
|
16
|
+
"""
|
17
|
+
|
18
|
+
"""
|
19
|
+
======================================================================
|
20
|
+
GEDCOM Module Types
|
21
|
+
======================================================================
|
22
|
+
"""
|
23
|
+
from .gedcom5x import Gedcom5xRecord
|
24
|
+
from .fact import Fact, FactType
|
25
|
+
from .event import Event, EventType
|
26
|
+
#=====================================================================
|
4
27
|
|
5
28
|
fact_event_table = {
|
6
29
|
# Person Fact / Event Types
|
@@ -178,33 +201,34 @@ fact_event_table = {
|
|
178
201
|
}
|
179
202
|
|
180
203
|
class GedcomXObject:
|
181
|
-
def __init__(self,record:
|
182
|
-
self.
|
183
|
-
self.
|
184
|
-
self.
|
204
|
+
def __init__(self,record: Gedcom5xRecord) -> None:
|
205
|
+
self.record = record
|
206
|
+
self.created_with_tag: str | None = record.tag if record and isinstance(record, Gedcom5xRecord) else None
|
207
|
+
self.created_at_level: int | None = record.level if record and isinstance(record, Gedcom5xRecord) else None
|
208
|
+
self.created_at_line_number: int | None = record.line if record and isinstance(record, Gedcom5xRecord) else None
|
185
209
|
|
186
210
|
class GedcomXSourceOrDocument(GedcomXObject):
|
187
|
-
def __init__(self,record:
|
211
|
+
def __init__(self,record: Gedcom5xRecord) -> None:
|
188
212
|
super().__init__(record)
|
189
|
-
self.title: str = None
|
190
|
-
self.citation: str = None
|
191
|
-
self.page: str = None
|
192
|
-
self.contributor: str = None
|
193
|
-
self.publisher: str = None
|
194
|
-
self.rights: str = None
|
195
|
-
self.url: str = None
|
196
|
-
self.medium: str = None
|
197
|
-
self.type: str = None
|
198
|
-
self.format: str = None
|
199
|
-
self.created: str = None
|
200
|
-
self.modified: str = None
|
201
|
-
self.language: str = None
|
202
|
-
self.relation: str = None
|
203
|
-
self.identifier: str = None
|
204
|
-
self.description: str = None
|
213
|
+
self.title: str | None = None
|
214
|
+
self.citation: str | None = None
|
215
|
+
self.page: str | None = None
|
216
|
+
self.contributor: str | None = None
|
217
|
+
self.publisher: str | None = None
|
218
|
+
self.rights: str | None = None
|
219
|
+
self.url: str | None = None
|
220
|
+
self.medium: str | None = None
|
221
|
+
self.type: str | None = None
|
222
|
+
self.format: str | None = None
|
223
|
+
self.created: str | None = None
|
224
|
+
self.modified: str | None = None
|
225
|
+
self.language: str | None = None
|
226
|
+
self.relation: str | None = None
|
227
|
+
self.identifier: str | None = None
|
228
|
+
self.description: str | None = None
|
205
229
|
|
206
230
|
class GedcomXEventOrFact(GedcomXObject):
|
207
|
-
def __new__(cls,record:
|
231
|
+
def __new__(cls,record: Gedcom5xRecord, object_stack: dict | None = None) -> object:
|
208
232
|
super().__init__(record)
|
209
233
|
if record.tag in fact_event_table.keys():
|
210
234
|
|
@@ -219,7 +243,7 @@ class GedcomXEventOrFact(GedcomXObject):
|
|
219
243
|
raise ValueError(f"{record.tag} not found in map")
|
220
244
|
|
221
245
|
class GedcomXRelationshipBuilder(GedcomXObject):
|
222
|
-
def __new__(cls,record:
|
246
|
+
def __new__(cls,record: Gedcom5xRecord | None = None, object_stack: dict | None = None) -> object:
|
223
247
|
last_relationship = object_stack.get('lastrelationship',None)
|
224
248
|
last_relationship_data = object_stack.get('lastrelationshipdata',None)
|
225
249
|
if not isinstance(last_relationship_data,dict):
|
gedcomx/name.py
ADDED
@@ -0,0 +1,396 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import List,Optional
|
3
|
+
|
4
|
+
"""
|
5
|
+
======================================================================
|
6
|
+
Project: Gedcom-X
|
7
|
+
File: Name.py
|
8
|
+
Author: David J. Cartwright
|
9
|
+
Purpose: Python Object representation of GedcomX Name, NameType, NameForm, NamePart Types
|
10
|
+
|
11
|
+
Created: 2025-08-25
|
12
|
+
Updated:
|
13
|
+
- 2025-08-31: _as_dict_ to only create entries in dict for fields that hold data
|
14
|
+
|
15
|
+
======================================================================
|
16
|
+
"""
|
17
|
+
|
18
|
+
"""
|
19
|
+
======================================================================
|
20
|
+
GEDCOM Module Types
|
21
|
+
======================================================================
|
22
|
+
"""
|
23
|
+
#======================================================================
|
24
|
+
from .attribution import Attribution
|
25
|
+
from .conclusion import Conclusion, ConfidenceLevel
|
26
|
+
from .date import Date
|
27
|
+
from .document import Document
|
28
|
+
from .note import Note
|
29
|
+
from .resource import Resource
|
30
|
+
from .source_reference import SourceReference
|
31
|
+
#======================================================================
|
32
|
+
|
33
|
+
|
34
|
+
class NameType(Enum):
|
35
|
+
BirthName = "http://gedcomx.org/BirthName"
|
36
|
+
MarriedName = "http://gedcomx.org/MarriedName"
|
37
|
+
AlsoKnownAs = "http://gedcomx.org/AlsoKnownAs"
|
38
|
+
Nickname = "http://gedcomx.org/Nickname"
|
39
|
+
AdoptiveName = "http://gedcomx.org/AdoptiveName"
|
40
|
+
FormalName = "http://gedcomx.org/FormalName"
|
41
|
+
ReligiousName = "http://gedcomx.org/ReligiousName"
|
42
|
+
|
43
|
+
@property
|
44
|
+
def description(self):
|
45
|
+
descriptions = {
|
46
|
+
NameType.BirthName: "Name given at birth.",
|
47
|
+
NameType.MarriedName: "Name accepted at marriage.",
|
48
|
+
NameType.AlsoKnownAs: "\"Also known as\" name.",
|
49
|
+
NameType.Nickname: "Nickname.",
|
50
|
+
NameType.AdoptiveName: "Name given at adoption.",
|
51
|
+
NameType.FormalName: "A formal name, usually given to distinguish it from a name more commonly used.",
|
52
|
+
NameType.ReligiousName: "A name given at a religious rite or ceremony."
|
53
|
+
}
|
54
|
+
return descriptions.get(self, "No description available.")
|
55
|
+
|
56
|
+
class NamePartQualifier(Enum):
|
57
|
+
Title = "http://gedcomx.org/Title"
|
58
|
+
Primary = "http://gedcomx.org/Primary"
|
59
|
+
Secondary = "http://gedcomx.org/Secondary"
|
60
|
+
Middle = "http://gedcomx.org/Middle"
|
61
|
+
Familiar = "http://gedcomx.org/Familiar"
|
62
|
+
Religious = "http://gedcomx.org/Religious"
|
63
|
+
Family = "http://gedcomx.org/Family"
|
64
|
+
Maiden = "http://gedcomx.org/Maiden"
|
65
|
+
Patronymic = "http://gedcomx.org/Patronymic"
|
66
|
+
Matronymic = "http://gedcomx.org/Matronymic"
|
67
|
+
Geographic = "http://gedcomx.org/Geographic"
|
68
|
+
Occupational = "http://gedcomx.org/Occupational"
|
69
|
+
Characteristic = "http://gedcomx.org/Characteristic"
|
70
|
+
Postnom = "http://gedcomx.org/Postnom"
|
71
|
+
Particle = "http://gedcomx.org/Particle"
|
72
|
+
RootName = "http://gedcomx.org/RootName"
|
73
|
+
|
74
|
+
@property
|
75
|
+
def description(self):
|
76
|
+
descriptions = {
|
77
|
+
NamePartQualifier.Title: "A designation for honorifics (e.g., Dr., Rev., His Majesty, Haji), ranks (e.g., Colonel, General), positions (e.g., Count, Chief), or other titles (e.g., PhD, MD). Name part qualifiers of type Title SHOULD NOT provide a value.",
|
78
|
+
NamePartQualifier.Primary: "A designation for the most prominent name among names of that type (e.g., the primary given name). Name part qualifiers of type Primary SHOULD NOT provide a value.",
|
79
|
+
NamePartQualifier.Secondary: "A designation for a name that is not primary in its importance among names of that type. Name part qualifiers of type Secondary SHOULD NOT provide a value.",
|
80
|
+
NamePartQualifier.Middle: "Useful for cultures designating a middle name distinct from a given name and surname. Name part qualifiers of type Middle SHOULD NOT provide a value.",
|
81
|
+
NamePartQualifier.Familiar: "A designation for one's familiar name. Name part qualifiers of type Familiar SHOULD NOT provide a value.",
|
82
|
+
NamePartQualifier.Religious: "A name given for religious purposes. Name part qualifiers of type Religious SHOULD NOT provide a value.",
|
83
|
+
NamePartQualifier.Family: "A name that associates a person with a group, such as a clan, tribe, or patriarchal hierarchy. Name part qualifiers of type Family SHOULD NOT provide a value.",
|
84
|
+
NamePartQualifier.Maiden: "Original surname retained by women after adopting a new surname upon marriage. Name part qualifiers of type Maiden SHOULD NOT provide a value.",
|
85
|
+
NamePartQualifier.Patronymic: "A name derived from a father or paternal ancestor. Name part qualifiers of type Patronymic SHOULD NOT provide a value.",
|
86
|
+
NamePartQualifier.Matronymic: "A name derived from a mother or maternal ancestor. Name part qualifiers of type Matronymic SHOULD NOT provide a value.",
|
87
|
+
NamePartQualifier.Geographic: "A name derived from associated geography. Name part qualifiers of type Geographic SHOULD NOT provide a value.",
|
88
|
+
NamePartQualifier.Occupational: "A name derived from one's occupation. Name part qualifiers of type Occupational SHOULD NOT provide a value.",
|
89
|
+
NamePartQualifier.Characteristic: "A name derived from a characteristic. Name part qualifiers of type Characteristic SHOULD NOT provide a value.",
|
90
|
+
NamePartQualifier.Postnom: "A name mandated by law for populations in specific regions. Name part qualifiers of type Postnom SHOULD NOT provide a value.",
|
91
|
+
NamePartQualifier.Particle: "A grammatical designation for articles, prepositions, conjunctions, and other words used as name parts. Name part qualifiers of type Particle SHOULD NOT provide a value.",
|
92
|
+
NamePartQualifier.RootName: "The 'root' of a name part, as distinguished from prefixes or suffixes (e.g., the root of 'Wilkówna' is 'Wilk'). A RootName qualifier MUST provide a value property."
|
93
|
+
}
|
94
|
+
return descriptions.get(self, "No description available.")
|
95
|
+
|
96
|
+
class NamePartType(Enum):
|
97
|
+
Prefix = "http://gedcomx.org/Prefix"
|
98
|
+
Suffix = "http://gedcomx.org/Suffix"
|
99
|
+
Given = "http://gedcomx.org/Given"
|
100
|
+
Surname = "http://gedcomx.org/Surname"
|
101
|
+
|
102
|
+
@property
|
103
|
+
def description(self):
|
104
|
+
descriptions = {
|
105
|
+
NamePartType.Prefix: "A name prefix.",
|
106
|
+
NamePartType.Suffix: "A name suffix.",
|
107
|
+
NamePartType.Given: "A given name.",
|
108
|
+
NamePartType.Surname: "A surname."
|
109
|
+
}
|
110
|
+
return descriptions.get(self, "No description available.")
|
111
|
+
|
112
|
+
class NamePart:
|
113
|
+
"""Used to model a portion of a full name
|
114
|
+
including the terms that make up that portion. Some name parts may have qualifiers
|
115
|
+
to provide additional semantic meaning to the name part (e.g., "given name" or "surname").
|
116
|
+
|
117
|
+
Args:
|
118
|
+
type (NamePartType | None): Classification of this component of the name
|
119
|
+
(e.g., ``Given``, ``Surname``, ``Prefix``, ``Suffix``, ``Title``, ``Particle``).
|
120
|
+
value (str): The textual value for this part, without surrounding
|
121
|
+
punctuation (e.g., ``"John"``, ``"van"``, ``"III"``).
|
122
|
+
qualifiers (list[NamePartQualifier] | None): Optional qualifiers that refine
|
123
|
+
the meaning or usage of this part (e.g., language/script variants, initials).
|
124
|
+
|
125
|
+
Examples:
|
126
|
+
>>> from gedcomx.Name import *
|
127
|
+
>>> typ = NamePartType.Given
|
128
|
+
>>> given = "Moses"
|
129
|
+
>>> q = NamePartQualifier.Primary
|
130
|
+
>>> name = NamePart(type=typ,value=given,qualifiers=[q])
|
131
|
+
>>> print(name)
|
132
|
+
NamePart(type=Given, value='Moses', qualifiers=1)
|
133
|
+
|
134
|
+
"""
|
135
|
+
identifier = 'http://gedcomx.org/v1/NamePart'
|
136
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
137
|
+
|
138
|
+
def __init__(self,
|
139
|
+
type: Optional[NamePartType] = None,
|
140
|
+
value: Optional[str] = None,
|
141
|
+
qualifiers: Optional[List[NamePartQualifier]] = None) -> None:
|
142
|
+
self.type = type
|
143
|
+
self.value = value
|
144
|
+
self.qualifiers = qualifiers if qualifiers else []
|
145
|
+
|
146
|
+
@property
|
147
|
+
def _as_dict_(self):
|
148
|
+
from .serialization import Serialization
|
149
|
+
type_as_dict = {}
|
150
|
+
if self.type:
|
151
|
+
type_as_dict['type'] = self.type.value
|
152
|
+
if self.value:
|
153
|
+
type_as_dict['value'] = self.value
|
154
|
+
if self.qualifiers:
|
155
|
+
type_as_dict['qualifiers'] = [q.value for q in self.qualifiers]
|
156
|
+
Serialization.serialize_dict(type_as_dict)
|
157
|
+
|
158
|
+
@classmethod
|
159
|
+
def _from_json_(cls,data):
|
160
|
+
name_part =NamePart(type=NamePartType(data['type']) if 'type' in data else None,
|
161
|
+
value=data.get('value'),
|
162
|
+
qualifiers=[NamePartQualifier(q) for q in data.get('qualifiers')])
|
163
|
+
return name_part
|
164
|
+
|
165
|
+
def __eq__(self, other):
|
166
|
+
if not isinstance(other, NamePart):
|
167
|
+
return NotImplemented
|
168
|
+
return (self.type == other.type and
|
169
|
+
self.value == other.value and
|
170
|
+
self.qualifiers == other.qualifiers)
|
171
|
+
|
172
|
+
def __str__(self) -> str:
|
173
|
+
parts = []
|
174
|
+
if self.type is not None:
|
175
|
+
parts.append(f"type={getattr(self.type, 'name', str(self.type))}")
|
176
|
+
if self.value is not None:
|
177
|
+
parts.append(f"value={self.value!r}")
|
178
|
+
if self.qualifiers:
|
179
|
+
parts.append(f"qualifiers={len(self.qualifiers)}")
|
180
|
+
return f"NamePart({', '.join(parts)})" if parts else "NamePart()"
|
181
|
+
|
182
|
+
def __repr__(self) -> str:
|
183
|
+
if self.type is not None:
|
184
|
+
tcls = self.type.__class__.__name__
|
185
|
+
tname = getattr(self.type, "name", str(self.type))
|
186
|
+
tval = getattr(self.type, "value", self.type)
|
187
|
+
type_repr = f"<{tcls}.{tname}: {tval!r}>"
|
188
|
+
else:
|
189
|
+
type_repr = "None"
|
190
|
+
return (
|
191
|
+
f"{self.__class__.__name__}("
|
192
|
+
f"type={type_repr}, "
|
193
|
+
f"value={self.value!r}, "
|
194
|
+
f"qualifiers={self.qualifiers!r})"
|
195
|
+
)
|
196
|
+
|
197
|
+
class NameForm:
|
198
|
+
"""A representation of a name (a "name form")
|
199
|
+
within a given cultural context, such as a given language and script.
|
200
|
+
As names are captured (both in records or in applications), the terms
|
201
|
+
in the name are sometimes classified by type. For example, a certificate
|
202
|
+
of death might prompt for **"given name(s)"** and **"surname"**. The parts
|
203
|
+
list can be used to represent the terms in the name that have been classified.
|
204
|
+
|
205
|
+
Args:
|
206
|
+
lang (str | None): BCP-47 language tag for this name form (e.g., "en").
|
207
|
+
fullText (str | None): The full, unparsed name string as written in the source.
|
208
|
+
If provided, the name SHOULD be rendered as it would normally be spoken in
|
209
|
+
the applicable cultural context.
|
210
|
+
parts (list[NamePart] | None): Ordered structured components of the name
|
211
|
+
(e.g., Given, Surname). If provided, ``fullText`` may be omitted. If
|
212
|
+
provided, the list SHOULD be ordered such that the parts are in the order
|
213
|
+
they would normally be spoken in the applicable cultural context.
|
214
|
+
|
215
|
+
"""
|
216
|
+
identifier = 'http://gedcomx.org/v1/NameForm'
|
217
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
218
|
+
|
219
|
+
def __init__(self, lang: Optional[str] = None,
|
220
|
+
fullText: Optional[str] = None,
|
221
|
+
parts: Optional[List[NamePart]] = None) -> None:
|
222
|
+
|
223
|
+
self.lang = lang
|
224
|
+
self.fullText = fullText
|
225
|
+
self.parts = parts if parts else []
|
226
|
+
|
227
|
+
@property
|
228
|
+
def _as_dict_(self):
|
229
|
+
from .serialization import Serialization
|
230
|
+
type_as_dict = {}
|
231
|
+
if self.lang:
|
232
|
+
type_as_dict['lang'] = self.lang
|
233
|
+
if self.fullText:
|
234
|
+
type_as_dict['fullText'] = self.fullText
|
235
|
+
if self.parts:
|
236
|
+
type_as_dict['parts'] = [part._as_dict_ for part in self.parts if part]
|
237
|
+
return Serialization.serialize_dict(type_as_dict)
|
238
|
+
|
239
|
+
@classmethod
|
240
|
+
def _from_json_(cls, data: dict) -> "NameForm":
|
241
|
+
"""Build a NameForm from JSON-like dict."""
|
242
|
+
return cls(
|
243
|
+
lang=data.get("lang", "en"),
|
244
|
+
fullText=data.get("fullText"),
|
245
|
+
parts=[NamePart._from_json_(p) for p in ensure_list(data.get("parts"))],
|
246
|
+
)
|
247
|
+
|
248
|
+
def _fulltext_parts(self):
|
249
|
+
pass
|
250
|
+
|
251
|
+
class Name(Conclusion):
|
252
|
+
"""**Defines a name of a person.**
|
253
|
+
|
254
|
+
A Name is intended to represent a single variant of a person's name.
|
255
|
+
This means that nicknames, spelling variations, or other names
|
256
|
+
(often distinguishable by a name type) should be modeled with
|
257
|
+
separate instances of Name.
|
258
|
+
|
259
|
+
.. admonition:: Advanced details
|
260
|
+
:class: toggle
|
261
|
+
|
262
|
+
The name forms of a name contain alternate representations of the name.
|
263
|
+
A Name MUST contain at least one name form, presumably a representation
|
264
|
+
of the name that is considered proper and well formed in the person's native,
|
265
|
+
historical cultural context. Other name forms MAY be included, which can be
|
266
|
+
used to represent this name in contexts where the native name form is not easily
|
267
|
+
recognized and interpreted. Alternate forms are more likely in situations where
|
268
|
+
conclusions are being analyzed across cultural context boundaries that have both
|
269
|
+
language and writing script differences.
|
270
|
+
|
271
|
+
|
272
|
+
Attributes:
|
273
|
+
type (Optional[:class:`~gedcomx.NameType`]): Classification of the name
|
274
|
+
(e.g., BirthName, AlsoKnownAs).
|
275
|
+
nameForms (List[:class:`~gedcomx.NameForm`]): One or more structured
|
276
|
+
representations of the name (full text and parts).
|
277
|
+
date (Optional[:class:`~gedcomx.Date`]): Date context for this name
|
278
|
+
(e.g., when the name was used or recorded).
|
279
|
+
"""
|
280
|
+
identifier = 'http://gedcomx.org/v1/Name'
|
281
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
282
|
+
|
283
|
+
@staticmethod
|
284
|
+
def simple(text: str):
|
285
|
+
"""
|
286
|
+
Takes a string and returns a GedcomX Name Object
|
287
|
+
"""
|
288
|
+
if text:
|
289
|
+
text = text.replace("/","")
|
290
|
+
parts = text.rsplit(' ', 1)
|
291
|
+
|
292
|
+
# Assign val1 and val2 based on the split
|
293
|
+
given = parts[0] if len(parts) > 1 else ""
|
294
|
+
surname = parts[1] if len(parts) > 1 else parts[0]
|
295
|
+
|
296
|
+
# Remove any '/' characters from both val1 and val2
|
297
|
+
#given = given.replace('/', '')
|
298
|
+
#surname = surname.replace('/', '')
|
299
|
+
|
300
|
+
given_name_part = NamePart(type = NamePartType.Given, value=given)
|
301
|
+
surname_part = NamePart(type = NamePartType.Surname, value=surname)
|
302
|
+
|
303
|
+
name_form = NameForm(fullText=text,parts=[given_name_part,surname_part])
|
304
|
+
name = Name(type=NameType.BirthName,nameForms=[name_form])
|
305
|
+
else:
|
306
|
+
name = Name()
|
307
|
+
return name
|
308
|
+
|
309
|
+
def __init__(self, id: Optional[str] = None,
|
310
|
+
lang: Optional[str] = None,
|
311
|
+
sources: Optional[List[SourceReference]] = None,
|
312
|
+
analysis: Optional[Document |Resource] = None,
|
313
|
+
notes: Optional[List[Note]] = None,
|
314
|
+
confidence: Optional[ConfidenceLevel] = None,
|
315
|
+
attribution: Optional[Attribution] = None,
|
316
|
+
type: Optional[NameType] = None,
|
317
|
+
nameForms: Optional[List[NameForm]]= None,
|
318
|
+
date: Optional[Date] = None) -> None:
|
319
|
+
super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
|
320
|
+
self.type = type
|
321
|
+
self.nameForms = nameForms if nameForms else []
|
322
|
+
self.date = date
|
323
|
+
|
324
|
+
def _add_name_part(self, namepart: NamePart):
|
325
|
+
if namepart and isinstance(namepart, NamePart):
|
326
|
+
for current_namepart in self.nameForms[0].parts:
|
327
|
+
if namepart == current_namepart:
|
328
|
+
return False
|
329
|
+
self.nameForms[0].parts.append(namepart)
|
330
|
+
|
331
|
+
@property
|
332
|
+
def _as_dict_(self):
|
333
|
+
from .serialization import Serialization
|
334
|
+
type_as_dict = super()._as_dict_
|
335
|
+
if self.type:
|
336
|
+
type_as_dict['type'] = getattr(self.type, 'value', self.type)
|
337
|
+
if self.nameForms:
|
338
|
+
type_as_dict['nameForms'] = [nf._as_dict_ for nf in self.nameForms if nf]
|
339
|
+
if self.date:
|
340
|
+
type_as_dict['date'] = self.date._as_dict_
|
341
|
+
|
342
|
+
return Serialization.serialize_dict(type_as_dict)
|
343
|
+
|
344
|
+
@classmethod
|
345
|
+
def _from_json_(cls, data: dict) -> "Name":
|
346
|
+
"""Build a Name from JSON-like dict."""
|
347
|
+
return cls(
|
348
|
+
id=data.get("id"),
|
349
|
+
lang=data.get("lang", "en"),
|
350
|
+
sources=[SourceReference._from_json_(s) for s in ensure_list(data.get("sources"))],
|
351
|
+
analysis=Resource._from_json_(data["analysis"]) if data.get("analysis") else None,
|
352
|
+
notes=[Note._from_json_(n) for n in ensure_list(data.get("notes"))],
|
353
|
+
confidence=ConfidenceLevel._from_json_(data["confidence"]) if data.get("confidence") else None,
|
354
|
+
attribution=Attribution._from_json_(data["attribution"]) if data.get("attribution") else None,
|
355
|
+
type=NameType(data["type"]) if data.get("type") else None,
|
356
|
+
nameForms=[NameForm._from_json_(nf) for nf in ensure_list(data.get("nameForms"))],
|
357
|
+
date=Date._from_json_(data["date"]) if data.get("date") else None,
|
358
|
+
)
|
359
|
+
|
360
|
+
def __str__(self) -> str:
|
361
|
+
"""Return a human-readable string for the Name-like object."""
|
362
|
+
return f"Name(id={self.id}, type={self.type}, forms={len(self.nameForms)}, date={self.date})"
|
363
|
+
|
364
|
+
def __repr__(self) -> str:
|
365
|
+
"""Return an unambiguous string representation of the Name-like object."""
|
366
|
+
return (
|
367
|
+
f"{self.__class__.__name__}("
|
368
|
+
f"id={self.id!r}, "
|
369
|
+
f"lang={self.lang!r}, "
|
370
|
+
f"sources={self.sources!r}, "
|
371
|
+
f"analysis={self.analysis!r}, "
|
372
|
+
f"notes={self.notes!r}, "
|
373
|
+
f"confidence={self.confidence!r}, "
|
374
|
+
f"attribution={self.attribution!r}, "
|
375
|
+
f"type={self.type!r}, "
|
376
|
+
f"nameForms={self.nameForms!r}, "
|
377
|
+
f"date={self.date!r})"
|
378
|
+
)
|
379
|
+
|
380
|
+
class QuickName():
|
381
|
+
def __new__(cls,name: str) -> Name:
|
382
|
+
obj = Name(nameForms=[NameForm(fullText=name)])
|
383
|
+
return obj
|
384
|
+
|
385
|
+
def ensure_list(val):
|
386
|
+
if val is None:
|
387
|
+
return []
|
388
|
+
return val if isinstance(val, list) else [val]
|
389
|
+
|
390
|
+
|
391
|
+
|
392
|
+
|
393
|
+
|
394
|
+
|
395
|
+
|
396
|
+
|
gedcomx/{Note.py → note.py}
RENAMED
@@ -1,7 +1,6 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
|
3
|
-
from .
|
4
|
-
from .Serialization import Serialization
|
3
|
+
from .attribution import Attribution
|
5
4
|
|
6
5
|
class Note:
|
7
6
|
identifier = 'http://gedcomx.org/v1/Note'
|
@@ -15,20 +14,28 @@ class Note:
|
|
15
14
|
|
16
15
|
def append(self, text_to_add: str):
|
17
16
|
if text_to_add and isinstance(text_to_add, str):
|
18
|
-
|
17
|
+
if self.text:
|
18
|
+
self.text = self.text + text_to_add
|
19
|
+
else:
|
20
|
+
self.text = text_to_add
|
19
21
|
else:
|
20
22
|
return #TODO
|
21
23
|
raise ValueError("The text to add must be a non-empty string.")
|
22
24
|
|
23
25
|
@property
|
24
26
|
def _as_dict_(self):
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
from .serialization import Serialization
|
28
|
+
type_as_dict = {}
|
29
|
+
if self.lang:
|
30
|
+
type_as_dict["lang"] = self.lang
|
31
|
+
if self.subject:
|
32
|
+
type_as_dict["subject"] = self.subject
|
33
|
+
if self.text:
|
34
|
+
type_as_dict["text"] = self.text
|
35
|
+
if self.attribution:
|
36
|
+
# If attribution exposes `_as_dict_` as a property, use it; otherwise include as-is
|
37
|
+
type_as_dict["attribution"] = getattr(self.attribution, "_as_dict_", self.attribution)
|
38
|
+
return Serialization.serialize_dict(type_as_dict)
|
32
39
|
|
33
40
|
def __eq__(self, other):
|
34
41
|
if not isinstance(other, Note):
|