gedcom-x 0.5.2__tar.gz → 0.5.5__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.
Files changed (52) hide show
  1. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/PKG-INFO +1 -1
  2. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcom_x.egg-info/PKG-INFO +1 -1
  3. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcom_x.egg-info/SOURCES.txt +4 -3
  4. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Address.py +2 -0
  5. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Agent.py +9 -2
  6. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Attribution.py +10 -46
  7. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Conclusion.py +85 -21
  8. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Coverage.py +10 -0
  9. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Date.py +2 -7
  10. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Document.py +27 -6
  11. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Event.py +20 -1
  12. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Exceptions.py +6 -0
  13. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Fact.py +7 -8
  14. gedcom_x-0.5.5/gedcomx/Gedcom.py +53 -0
  15. gedcom_x-0.5.2/gedcomx/Gedcom.py → gedcom_x-0.5.5/gedcomx/Gedcom5x.py +226 -87
  16. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/GedcomX.py +37 -22
  17. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Gender.py +6 -40
  18. gedcom_x-0.5.5/gedcomx/Identifier.py +226 -0
  19. gedcom_x-0.5.5/gedcomx/Mutations.py +228 -0
  20. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Name.py +6 -0
  21. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Person.py +49 -90
  22. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/PlaceDescription.py +23 -14
  23. gedcom_x-0.5.5/gedcomx/PlaceReference.py +30 -0
  24. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Relationship.py +23 -54
  25. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Resource.py +17 -3
  26. gedcom_x-0.5.5/gedcomx/Serialization.py +401 -0
  27. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/SourceDescription.py +6 -9
  28. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/SourceReference.py +20 -86
  29. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Subject.py +4 -4
  30. gedcom_x-0.5.5/gedcomx/Translation.py +219 -0
  31. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/URI.py +1 -0
  32. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/__init__.py +7 -1
  33. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/pyproject.toml +1 -1
  34. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/setup.py +1 -1
  35. gedcom_x-0.5.2/gedcomx/Identifier.py +0 -172
  36. gedcom_x-0.5.2/gedcomx/PlaceReference.py +0 -33
  37. gedcom_x-0.5.2/gedcomx/Serialization.py +0 -80
  38. gedcom_x-0.5.2/gedcomx/_Links.py +0 -37
  39. gedcom_x-0.5.2/gedcomx/g7interop.py +0 -205
  40. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcom_x.egg-info/dependency_links.txt +0 -0
  41. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcom_x.egg-info/top_level.txt +0 -0
  42. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/EvidenceReference.py +0 -0
  43. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Group.py +0 -0
  44. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Logging.py +0 -0
  45. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Note.py +0 -0
  46. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/OnlineAccount.py +0 -0
  47. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Qualifier.py +0 -0
  48. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/SourceCitation.py +0 -0
  49. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/TextValue.py +0 -0
  50. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/TopLevelTypeCollection.py +0 -0
  51. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/gedcomx/Zip.py +0 -0
  52. {gedcom_x-0.5.2 → gedcom_x-0.5.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gedcom-x
3
- Version: 0.5.2
3
+ Version: 0.5.5
4
4
  Summary: Python implimentation of gedcom-x standard
5
5
  Author-email: "David J. Cartwright" <davidcartwright@hotmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gedcom-x
3
- Version: 0.5.2
3
+ Version: 0.5.5
4
4
  Summary: Python implimentation of gedcom-x standard
5
5
  Author-email: "David J. Cartwright" <davidcartwright@hotmail.com>
6
6
  License: MIT
@@ -16,11 +16,13 @@ gedcomx/EvidenceReference.py
16
16
  gedcomx/Exceptions.py
17
17
  gedcomx/Fact.py
18
18
  gedcomx/Gedcom.py
19
+ gedcomx/Gedcom5x.py
19
20
  gedcomx/GedcomX.py
20
21
  gedcomx/Gender.py
21
22
  gedcomx/Group.py
22
23
  gedcomx/Identifier.py
23
24
  gedcomx/Logging.py
25
+ gedcomx/Mutations.py
24
26
  gedcomx/Name.py
25
27
  gedcomx/Note.py
26
28
  gedcomx/OnlineAccount.py
@@ -37,8 +39,7 @@ gedcomx/SourceReference.py
37
39
  gedcomx/Subject.py
38
40
  gedcomx/TextValue.py
39
41
  gedcomx/TopLevelTypeCollection.py
42
+ gedcomx/Translation.py
40
43
  gedcomx/URI.py
41
44
  gedcomx/Zip.py
42
- gedcomx/_Links.py
43
- gedcomx/__init__.py
44
- gedcomx/g7interop.py
45
+ gedcomx/__init__.py
@@ -61,6 +61,8 @@ class Address:
61
61
 
62
62
  @value.setter
63
63
  def value(self,value: str):
64
+ self._value = value
65
+ return
64
66
  raise NotImplementedError("Parsing of a full address is not implimented.")
65
67
 
66
68
  def _append(self,value):
@@ -139,6 +139,14 @@ class Agent:
139
139
  }
140
140
  return Serialization.serialize_dict(type_as_dict)
141
141
 
142
+ @classmethod
143
+ def _from_json_(cls, data: dict):
144
+ """
145
+ Create a Person instance from a JSON-dict (already parsed).
146
+ """
147
+ type_as_dict = Serialization.get_class_fields('Agent')
148
+ return Serialization.deserialize(data, type_as_dict)
149
+
142
150
  def __str__(self):
143
151
  """
144
152
  Return a human-readable string representation of the Agent.
@@ -179,8 +187,7 @@ class Agent:
179
187
  self.uri == other.uri
180
188
  )
181
189
  '''
182
- print(other)
183
- print(self)
190
+
184
191
  self_names = {n.value for n in self.names if hasattr(n, "value")}
185
192
  other_names = {n.value for n in other.names if hasattr(n, "value")}
186
193
  if self_names & other_names: # intersection not empty
@@ -10,11 +10,11 @@ class Attribution:
10
10
  """Attribution Information for a Genealogy, Conclusion, Subject and child classes
11
11
 
12
12
  Args:
13
- contributor (Agent): Contributor to object being attributed.
14
- modified (timestamp): timestamp for when this record was modified.
15
- changeMessage (str): Birth date (YYYY-MM-DD).
13
+ contributor (Agent, optional): Contributor to object being attributed.
14
+ modified (timestamp, optional): timestamp for when this record was modified.
15
+ changeMessage (str, optional): Birth date (YYYY-MM-DD).
16
16
  creator (Agent, optional): Creator of object being attributed.
17
- created (timestamp): timestamp for when this record was created
17
+ created (timestamp, optional): timestamp for when this record was created
18
18
 
19
19
  Raises:
20
20
 
@@ -56,45 +56,9 @@ class Attribution:
56
56
  """
57
57
  # contributor
58
58
 
59
- contrib = None
60
- if 'contributor' in data:
61
- contrib_data = data['contributor']
62
-
63
- contrib = Resource(contrib_data['resource'],contrib_data['resourceId'])
64
-
65
- # creator
66
- creat = None
67
- if 'creator' in data:
68
- raw = data['creator']
69
- if isinstance(raw, dict):
70
- creat = Resource._from_json_(raw)
71
- elif isinstance(raw, str):
72
- creat = Resource(uri=raw)
73
-
74
- # parse created date
75
- raw_created = data.get('created')
76
- if isinstance(raw_created, (int, float)):
77
- created_dt = datetime.fromtimestamp(raw_created / 1000.0)
78
- elif isinstance(raw_created, str):
79
- created_dt = datetime.fromisoformat(raw_created)
80
- else:
81
- created_dt = None
82
-
83
- # parse modified date
84
- raw_modified = data.get('modified')
85
- if isinstance(raw_modified, (int, float)):
86
- modified_dt = datetime.fromtimestamp(raw_modified / 1000.0)
87
- elif isinstance(raw_modified, str):
88
- modified_dt = datetime.fromisoformat(raw_modified)
89
- else:
90
- modified_dt = None
91
-
92
- change_msg = data.get('changeMessage')
93
-
94
- return cls(
95
- contributor=contrib,
96
- created=created_dt,
97
- creator=creat,
98
- modified=modified_dt,
99
- changeMessage=change_msg
100
- )
59
+ """
60
+ Create a Person instance from a JSON-dict (already parsed).
61
+ """
62
+ from .Serialization import Serialization
63
+
64
+ return Serialization.deserialize(data, Attribution)
@@ -11,7 +11,7 @@ from .Qualifier import Qualifier
11
11
  from .Serialization import Serialization
12
12
  from .SourceReference import SourceReference
13
13
  from .Resource import Resource, URI
14
- from ._Links import _LinkList, _Link
14
+ from .Extensions.rs10.rsLink import _rsLinkList, rsLink
15
15
 
16
16
  from collections.abc import Sized
17
17
 
@@ -19,15 +19,72 @@ class ConfidenceLevel(Qualifier):
19
19
  High = "http://gedcomx.org/High"
20
20
  Medium = "http://gedcomx.org/Medium"
21
21
  Low = "http://gedcomx.org/Low"
22
-
22
+
23
+ _NAME_TO_URI = {
24
+ "high": High,
25
+ "medium": Medium,
26
+ "low": Low,
27
+ }
28
+
29
+ @classmethod
30
+ def _from_json_(cls, data):
31
+ """
32
+ Accepts:
33
+ - "High" | "Medium" | "Low"
34
+ - "http://gedcomx.org/High" | ".../Medium" | ".../Low"
35
+ - {"type": "..."} or {"value": "..."} or {"confidence": "..."} or {"level": "..."} or {"uri": "..."}
36
+ - existing ConfidenceLevel instance
37
+ Returns:
38
+ ConfidenceLevel instance with .value set to the canonical URI.
39
+ """
40
+ if data is None:
41
+ return None
42
+
43
+ if isinstance(data, cls):
44
+ return data
45
+
46
+ # Extract token from dicts or use the raw scalar
47
+ if isinstance(data, dict):
48
+ token = (
49
+ data.get("confidence")
50
+ or data.get("type")
51
+ or data.get("value")
52
+ or data.get("level")
53
+ or data.get("uri")
54
+ )
55
+ else:
56
+ token = data
57
+
58
+ if token is None:
59
+ return None
60
+
61
+ token_str = str(token).strip()
62
+
63
+ # Normalize to canonical URI
64
+ if token_str.lower() in cls._NAME_TO_URI:
65
+ uri = cls._NAME_TO_URI[token_str.lower()]
66
+ elif token_str in (cls.High, cls.Medium, cls.Low):
67
+ uri = token_str
68
+ else:
69
+ raise ValueError(f"Unknown ConfidenceLevel: {token!r}")
70
+
71
+ # Create a ConfidenceLevel instance without invoking Qualifier.__init__
72
+ obj = cls.__new__(cls)
73
+ # store the canonical URI on the instance; used by description and (optionally) serialization
74
+ obj.value = uri
75
+ return obj
76
+
23
77
  @property
24
78
  def description(self):
25
79
  descriptions = {
26
- ConfidenceLevel.High: "The contributor has a high degree of confidence that the assertion is true.",
27
- ConfidenceLevel.Medium: "The contributor has a medium degree of confidence that the assertion is true.",
28
- ConfidenceLevel.Low: "The contributor has a low degree of confidence that the assertion is true."
80
+ self.High: "The contributor has a high degree of confidence that the assertion is true.",
81
+ self.Medium: "The contributor has a medium degree of confidence that the assertion is true.",
82
+ self.Low: "The contributor has a low degree of confidence that the assertion is true."
29
83
  }
30
- return descriptions.get(self, "No description available.")
84
+ # Works whether the instance holds .value or (edge-case) if `self` is compared directly
85
+ key = getattr(self, "value", self)
86
+ return descriptions.get(key, "No description available.")
87
+
31
88
 
32
89
  class Conclusion:
33
90
  """
@@ -53,13 +110,7 @@ class Conclusion:
53
110
  uri (Resource, optional): A URI reference for the conclusion. Defaults to a
54
111
  URI with the fragment set to the `id`.
55
112
  links (_LinkList, optional): A list of links associated with the conclusion.
56
- Defaults to an empty `_LinkList`.
57
-
58
- Methods:
59
- add_note(note_to_add): Adds a note if it is not a duplicate and does not
60
- exceed the maximum allowed notes.
61
- add_source(source_to_add): Adds a source reference if it is not already present.
62
- add_link(link): Adds a link to the `_LinkList`.
113
+ Defaults to an empty `_LinkList`.
63
114
  """
64
115
  identifier = 'http://gedcomx.org/v1/Conclusion'
65
116
  version = 'http://gedcomx.org/conceptual-model/v1'
@@ -75,20 +126,20 @@ class Conclusion:
75
126
  return short_uuid
76
127
 
77
128
  def __init__(self,
78
- id: Optional[str],
129
+ id: Optional[str] = None,
79
130
  lang: Optional[str] = 'en',
80
131
  sources: Optional[List[SourceReference]] = None,
81
132
  analysis: Optional[object | Resource] = None,
82
- notes: Optional[List[Note]] = [],
133
+ notes: Optional[List[Note]] = None,
83
134
  confidence: Optional[ConfidenceLevel] = None,
84
135
  attribution: Optional[Attribution] = None,
85
136
  uri: Optional[Resource] = None,
86
137
  _max_note_count: int = 20,
87
- links: Optional[_LinkList] = None) -> None:
138
+ links: Optional[_rsLinkList] = None) -> None:
88
139
 
89
140
  self._id_generator = Conclusion.default_id_generator
90
141
 
91
- self.id = id if id else self._id_generator()
142
+ self.id = id if id else None
92
143
  self.lang = lang
93
144
  self.sources = sources if sources else []
94
145
  self.analysis = analysis
@@ -96,8 +147,8 @@ class Conclusion:
96
147
  self.confidence = confidence
97
148
  self.attribution = attribution
98
149
  self.max_note_count = _max_note_count
99
- self.uri = uri if uri else URI(fragment=id)
100
- self.links = links if links else _LinkList() #NOTE This is not in specification, following FS format
150
+ self.uri = uri if uri else URI(fragment=id if id else self.id)
151
+ self.links = links if links else _rsLinkList() #NOTE This is not in specification, following FS format
101
152
 
102
153
  def add_note(self,note_to_add: Note):
103
154
  if self.notes and len(self.notes) >= self.max_note_count:
@@ -118,9 +169,22 @@ class Conclusion:
118
169
  else:
119
170
  raise ValueError()
120
171
 
121
- def add_link(self,link: _Link):
122
- if link and isinstance(link,_Link):
172
+ def add_link(self,link: rsLink) -> bool:
173
+ """
174
+ Adds a link to the Conclusion link list.
175
+
176
+ Args:
177
+ link (rsLink): The link to be added.
178
+
179
+ Returns:
180
+ bool: The return value. True for success, False otherwise.
181
+
182
+ Note: Duplicate checking not impimented at this level
183
+ """
184
+ if link and isinstance(link,rsLink):
123
185
  self.links.add(link)
186
+ return True
187
+ return False
124
188
 
125
189
  @property
126
190
  def _as_dict_(self):
@@ -2,6 +2,7 @@ from typing import Optional
2
2
 
3
3
  from .Date import Date
4
4
  from .PlaceReference import PlaceReference
5
+ from .Serialization import Serialization
5
6
 
6
7
  class Coverage:
7
8
  identifier = 'http://gedcomx.org/v1/Coverage'
@@ -13,6 +14,15 @@ class Coverage:
13
14
 
14
15
  # ...existing code...
15
16
 
17
+ @property
18
+ def _as_dict_(self):
19
+ type_as_dict = {
20
+ 'spatial': self.spatial if self.spatial else None,
21
+ 'temporal': self. temporal if self.temporal else None
22
+ }
23
+
24
+ return Serialization.serialize_dict(type_as_dict)
25
+
16
26
  @classmethod
17
27
  def _from_json_(cls, data: dict):
18
28
  """
@@ -21,7 +21,8 @@ class Date:
21
21
 
22
22
  self.normalized: DateNormalization | None = normalized if normalized else None
23
23
 
24
- def _prop_dict(self):
24
+ @property
25
+ def _as_dict_(self):
25
26
  return {'original': self.orginal,
26
27
  'formal': self.formal}
27
28
 
@@ -33,12 +34,6 @@ class Date:
33
34
  return Date(original=original,formal=formal)
34
35
 
35
36
 
36
- Date._to_dict_ = lambda self: {
37
- 'original': self.orginal,
38
- 'formal': self.formal}
39
-
40
-
41
-
42
37
 
43
38
  def date_to_timestamp(date_str: str, assume_utc_if_naive: bool = True, print_definition: bool = True):
44
39
  """
@@ -1,13 +1,13 @@
1
1
  from enum import Enum
2
2
  from typing import Optional, List
3
3
 
4
- from gedcomx.Attribution import Attribution
5
- #from gedcomx.Conclusion import ConfidenceLevel
6
- from gedcomx.Note import Note
7
- from gedcomx.SourceReference import SourceReference
8
- from gedcomx.Resource import Resource
4
+ from .Attribution import Attribution
5
+ from .Note import Note
6
+ from .SourceReference import SourceReference
7
+ from .Resource import Resource
9
8
 
10
9
  from .Conclusion import Conclusion
10
+ from .Serialization import Serialization
11
11
 
12
12
  class DocumentType(Enum):
13
13
  Abstract = "http://gedcomx.org/Abstract"
@@ -49,4 +49,25 @@ class Document(Conclusion):
49
49
  self.type = type
50
50
  self.extracted = extracted
51
51
  self.textType = textType
52
- self.text = text
52
+ self.text = text
53
+
54
+ @property
55
+ def _as_dict(self):
56
+ type_as_dict = super()._as_dict_
57
+ if self.type:
58
+ type_as_dict['type'] = self.type.value
59
+ if self.extracted is not None:
60
+ type_as_dict['extracted'] = self.extracted
61
+ if self.textType:
62
+ type_as_dict['textType'] = self.textType.value
63
+ if self.text:
64
+ type_as_dict['text'] = self.text
65
+ return Serialization.serialize_dict(type_as_dict)
66
+
67
+ @classmethod
68
+ def _from_json_(cls, data: dict):
69
+ """
70
+ Create a Person instance from a JSON-dict (already parsed).
71
+ """
72
+ type_as_dict = Serialization.get_class_fields('Document')
73
+ return Serialization.deserialize(data, type_as_dict)
@@ -9,6 +9,7 @@ from .Conclusion import Conclusion, ConfidenceLevel
9
9
  from .Date import Date
10
10
  from .Note import Note
11
11
  from .PlaceReference import PlaceReference
12
+ from .Serialization import Serialization
12
13
  from .SourceReference import SourceReference
13
14
  from .Subject import Subject
14
15
  from .Resource import Resource
@@ -84,6 +85,7 @@ class EventType(Enum):
84
85
  Naturalization = "http://gedcomx.org/Naturalization"
85
86
  Ordination = "http://gedcomx.org/Ordination"
86
87
  Retirement = "http://gedcomx.org/Retirement"
88
+ MarriageSettlment = 'https://gedcom.io/terms/v7/MARS'
87
89
 
88
90
  @property
89
91
  def description(self):
@@ -192,4 +194,21 @@ class Event(Subject):
192
194
  date: Optional[Date] = None,
193
195
  place: Optional[PlaceReference] = None,
194
196
  roles: Optional[List[EventRole]] = []) -> None:
195
- super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
197
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution, extracted, evidence, media, identifiers)
198
+
199
+ self.type = type if type and isinstance(type, EventType) else None
200
+ self.date = date if date and isinstance(date, Date) else None
201
+ self.place = place if place and isinstance(place, PlaceReference) else None
202
+ self.roles = roles if roles and isinstance(roles, list) else []
203
+
204
+ @property
205
+ def _as_dict_(self):
206
+ raise NotImplementedError("Not implemented yet")
207
+
208
+ @classmethod
209
+ def _from_json_(cls, data: dict):
210
+ """
211
+ Create a Person instance from a JSON-dict (already parsed).
212
+ """
213
+ type_as_dict = Serialization.get_class_fields('Event')
214
+ return Serialization.deserialize(data, type_as_dict)
@@ -3,6 +3,12 @@
3
3
  class GedcomXError(Exception):
4
4
  """Base for all app-specific errors."""
5
5
 
6
+ class GedcomClassAttributeError(GedcomXError):
7
+ def __init__(self, *args: object) -> None:
8
+ msg = f"This class need more information to be created: {args}"
9
+ super().__init__(msg)
10
+
11
+
6
12
  class TagConversionError(GedcomXError):
7
13
  def __init__(self, record,levelstack):
8
14
  msg = f"Cannot convert: #{record.line} TAG: {record.tag} {record.xref if record.xref else ''} Value:{record.value} STACK: {type(levelstack[record.level-1]).__name__}"
@@ -22,7 +22,7 @@ from enum import Enum
22
22
 
23
23
  from collections.abc import Sized
24
24
 
25
- from ._Links import _Link, _LinkList
25
+ from .Extensions.rs10.rsLink import rsLink, _rsLinkList
26
26
 
27
27
 
28
28
  class FactType(Enum):
@@ -401,14 +401,14 @@ class Fact(Conclusion):
401
401
  sources: Optional[List[SourceReference]] = [],
402
402
  analysis: Optional[Resource | Document] = None,
403
403
  notes: Optional[List[Note]] = [],
404
- confidence: ConfidenceLevel = None,
405
- attribution: Attribution = None,
406
- type: FactType = None,
404
+ confidence: Optional[ConfidenceLevel] = None,
405
+ attribution: Optional[Attribution] = None,
406
+ type: Optional[FactType] = None,
407
407
  date: Optional[Date] = None,
408
408
  place: Optional[PlaceReference] = None,
409
409
  value: Optional[str] = None,
410
410
  qualifiers: Optional[List[FactQualifier]] = None,
411
- links: Optional[_LinkList] = None):
411
+ links: Optional[_rsLinkList] = None):
412
412
  super().__init__(id, lang, sources, analysis, notes, confidence, attribution, links=links)
413
413
  self.type = type
414
414
  self.date = date
@@ -433,12 +433,11 @@ class Fact(Conclusion):
433
433
  # Only add Relationship-specific fields
434
434
  fact_dict.update( {
435
435
  'type': self.type.value if self.type else None,
436
- 'date': self.date._prop_dict() if self.date else None,
436
+ 'date': self.date._as_dict_ if self.date else None,
437
437
  'place': self.place._as_dict_ if self.place else None,
438
438
  'value': self.value,
439
439
  'qualifiers': [q.value for q in self.qualifiers] if self.qualifiers else []
440
440
  })
441
-
442
441
 
443
442
  return Serialization.serialize_dict(fact_dict)
444
443
 
@@ -463,7 +462,7 @@ class Fact(Conclusion):
463
462
  if data.get('place') else None)
464
463
  value = data.get('value')
465
464
  qualifiers = [Qualifier._from_json_(q) for q in data.get('qualifiers', [])]
466
- links = _LinkList._from_json_(data.get('links')) if data.get('links') else None
465
+ links = _rsLinkList._from_json_(data.get('links')) if data.get('links') else None
467
466
 
468
467
  return cls(
469
468
  id=id_,
@@ -0,0 +1,53 @@
1
+ import re
2
+
3
+ class Gedcom():
4
+ def __init__(self) -> None:
5
+ pass
6
+
7
+ @staticmethod
8
+ def read_gedcom_version(filepath: str) -> str | None:
9
+ """
10
+ Reads only the HEAD section of a GEDCOM file and returns the GEDCOM standard version.
11
+ Looks specifically for HEAD → GEDC → VERS.
12
+
13
+ Returns:
14
+ str: GEDCOM version (e.g., "5.5.1" or "7.0.0"), or None if not found.
15
+ """
16
+ version = None
17
+ inside_head = False
18
+ inside_gedc = False
19
+
20
+ with open(filepath, "r", encoding="utf-8") as f:
21
+ for line in f:
22
+ parts = line.strip().split(maxsplit=2)
23
+ if not parts:
24
+ continue
25
+
26
+ level = int(parts[0])
27
+ tag = parts[1] if len(parts) > 1 else ""
28
+ value = parts[2] if len(parts) > 2 else None
29
+
30
+ # Enter HEAD
31
+ if level == 0 and tag == "HEAD":
32
+ inside_head = True
33
+ continue
34
+
35
+ # Leave HEAD block
36
+ if inside_head and level == 0:
37
+ break
38
+
39
+ # Inside HEAD, look for GEDC
40
+ if inside_head and level == 1 and tag == "GEDC":
41
+ inside_gedc = True
42
+ continue
43
+
44
+ # If we drop back to level 1 (but not GEDC), stop looking inside GEDC
45
+ if inside_gedc and level == 1:
46
+ inside_gedc = False
47
+
48
+ # Inside GEDC, look for VERS
49
+ if inside_gedc and tag == "VERS":
50
+ version = value
51
+ break
52
+
53
+ return version