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.
Files changed (64) hide show
  1. {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.8.dist-info}/METADATA +1 -1
  2. gedcom_x-0.5.8.dist-info/RECORD +56 -0
  3. gedcomx/Extensions/__init__.py +1 -0
  4. gedcomx/Extensions/rs10/__init__.py +1 -0
  5. gedcomx/Extensions/rs10/rsLink.py +116 -0
  6. gedcomx/TopLevelTypeCollection.py +1 -1
  7. gedcomx/__init__.py +43 -41
  8. gedcomx/{Address.py → address.py} +13 -13
  9. gedcomx/{Agent.py → agent.py} +52 -24
  10. gedcomx/{Attribution.py → attribution.py} +36 -9
  11. gedcomx/{Conclusion.py → conclusion.py} +49 -21
  12. gedcomx/converter.py +1049 -0
  13. gedcomx/coverage.py +55 -0
  14. gedcomx/{Date.py → date.py} +11 -4
  15. gedcomx/{Document.py → document.py} +27 -8
  16. gedcomx/{Event.py → event.py} +102 -27
  17. gedcomx/{EvidenceReference.py → evidence_reference.py} +2 -2
  18. gedcomx/{Fact.py → fact.py} +45 -34
  19. gedcomx/{Gedcom5x.py → gedcom5x.py} +78 -61
  20. gedcomx/gedcom7/Exceptions.py +9 -0
  21. gedcomx/gedcom7/Gedcom7.py +160 -0
  22. gedcomx/gedcom7/GedcomStructure.py +94 -0
  23. gedcomx/gedcom7/Specification.py +347 -0
  24. gedcomx/gedcom7/__init__.py +26 -0
  25. gedcomx/gedcom7/g7interop.py +205 -0
  26. gedcomx/gedcom7/logger.py +19 -0
  27. gedcomx/gedcomx.py +501 -0
  28. gedcomx/{Gender.py → gender.py} +29 -17
  29. gedcomx/group.py +63 -0
  30. gedcomx/{Identifier.py → identifier.py} +13 -16
  31. gedcomx/{LoggingHub.py → logging_hub.py} +21 -0
  32. gedcomx/{Mutations.py → mutations.py} +50 -26
  33. gedcomx/name.py +396 -0
  34. gedcomx/{Note.py → note.py} +17 -10
  35. gedcomx/{OnlineAccount.py → online_account.py} +1 -1
  36. gedcomx/{Person.py → person.py} +52 -29
  37. gedcomx/place_description.py +123 -0
  38. gedcomx/place_reference.py +62 -0
  39. gedcomx/qualifier.py +54 -0
  40. gedcomx/{Relationship.py → relationship.py} +33 -13
  41. gedcomx/resource.py +85 -0
  42. gedcomx/serialization.py +815 -0
  43. gedcomx/{SourceDescription.py → source_description.py} +144 -85
  44. gedcomx/{SourceReference.py → source_reference.py} +15 -14
  45. gedcomx/{Subject.py → subject.py} +30 -28
  46. gedcomx/{GedcomX.py → translation.py} +283 -446
  47. gedcomx/{URI.py → uri.py} +42 -26
  48. gedcom_x-0.5.6.dist-info/RECORD +0 -45
  49. gedcomx/Coverage.py +0 -36
  50. gedcomx/Group.py +0 -37
  51. gedcomx/Name.py +0 -276
  52. gedcomx/PlaceDescription.py +0 -70
  53. gedcomx/PlaceReference.py +0 -30
  54. gedcomx/Qualifier.py +0 -27
  55. gedcomx/Resource.py +0 -75
  56. gedcomx/Serialization.py +0 -401
  57. gedcomx/Translation.py +0 -219
  58. {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.8.dist-info}/WHEEL +0 -0
  59. {gedcom_x-0.5.6.dist-info → gedcom_x-0.5.8.dist-info}/top_level.txt +0 -0
  60. /gedcomx/{Exceptions.py → exceptions.py} +0 -0
  61. /gedcomx/{ExtensibleEnum.py → extensible_enum.py} +0 -0
  62. /gedcomx/{Gedcom.py → gedcom.py} +0 -0
  63. /gedcomx/{SourceCitation.py → source_citation.py} +0 -0
  64. /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 .Resource import Resource
9
- from .URI import URI
10
- from .ExtensibleEnum import ExtensibleEnum
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 .Serialization import Serialization
54
- type_as_dict = {
55
- 'value': [v for v in self.values] if self.values else None,
56
- 'type': self.type.value if self.type else None
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
- from .Gedcom5x import GedcomRecord
2
- from .Fact import Fact, FactType
3
- from .Event import Event, EventType
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: GedcomRecord | None = None) -> None:
182
- self.created_with_tag: str = record.tag if record and isinstance(record, GedcomRecord) else None
183
- self.created_at_level: int = record.level if record and isinstance(record, GedcomRecord) else None
184
- self.created_at_line_number: int = record.line_number if record and isinstance(record, GedcomRecord) else None
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: GedcomRecord | None = None) -> None:
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: GedcomRecord | None = None, object_stack: dict | None = None) -> object:
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: GedcomRecord | None = None, object_stack: dict | None = None) -> object:
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
+
@@ -1,7 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from .Attribution import Attribution
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
- self.text = self.text + text_to_add
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
- note_dict = {
26
- "lang":self.lang if self.lang else None,
27
- "subject":self.subject if self.subject else None,
28
- "text":self.text if self.text else None,
29
- "attribution": self.attribution if self.attribution else None
30
- }
31
- return Serialization.serialize_dict(note_dict)
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):
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from .Resource import Resource
3
+ from .resource import Resource
4
4
 
5
5
  class OnlineAccount:
6
6
  identifier = 'http://gedcomx.org/v1/OnlineAccount'