gedcom-x 0.5__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.dist-info/METADATA +17 -0
- gedcom_x-0.5.dist-info/RECORD +37 -0
- gedcom_x-0.5.dist-info/WHEEL +5 -0
- gedcom_x-0.5.dist-info/top_level.txt +1 -0
- gedcomx/Address.py +100 -0
- gedcomx/Agent.py +83 -0
- gedcomx/Attribution.py +116 -0
- gedcomx/Conclusion.py +137 -0
- gedcomx/Coverage.py +26 -0
- gedcomx/Date.py +29 -0
- gedcomx/Document.py +42 -0
- gedcomx/Event.py +195 -0
- gedcomx/EvidenceReference.py +11 -0
- gedcomx/Fact.py +462 -0
- gedcomx/Gedcom.py +345 -0
- gedcomx/GedcomX.py +1105 -0
- gedcomx/Gender.py +48 -0
- gedcomx/Group.py +37 -0
- gedcomx/Identifier.py +89 -0
- gedcomx/Name.py +241 -0
- gedcomx/Note.py +65 -0
- gedcomx/OnlineAccount.py +10 -0
- gedcomx/Person.py +178 -0
- gedcomx/PlaceDescription.py +47 -0
- gedcomx/PlaceReference.py +31 -0
- gedcomx/Qualifier.py +27 -0
- gedcomx/Relationship.py +116 -0
- gedcomx/Serialization.py +37 -0
- gedcomx/SourceCitation.py +20 -0
- gedcomx/SourceDescription.py +241 -0
- gedcomx/SourceReference.py +168 -0
- gedcomx/Subject.py +73 -0
- gedcomx/TextValue.py +34 -0
- gedcomx/TopLevelTypeCollection.py +47 -0
- gedcomx/URI.py +70 -0
- gedcomx/_Resource.py +11 -0
- gedcomx/__init__.py +39 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
|
3
|
+
from .Attribution import Attribution
|
4
|
+
from .Qualifier import Qualifier
|
5
|
+
|
6
|
+
from .URI import URI
|
7
|
+
|
8
|
+
class KnownSourceReference(Qualifier):
|
9
|
+
CharacterRegion = "http://gedcomx.org/CharacterRegion"
|
10
|
+
RectangleRegion = "http://gedcomx.org/RectangleRegion"
|
11
|
+
TimeRegion = "http://gedcomx.org/TimeRegion"
|
12
|
+
Page = "http://gedcomx.org/Page"
|
13
|
+
|
14
|
+
@property
|
15
|
+
def description(self):
|
16
|
+
descriptions = {
|
17
|
+
self.CharacterRegion: (
|
18
|
+
"A region of text in a digital document, in the form of a,b where a is the index of the start "
|
19
|
+
"character and b is the index of the end character. The meaning of this qualifier is undefined "
|
20
|
+
"if the source being referenced is not a digital document."
|
21
|
+
),
|
22
|
+
self.RectangleRegion: (
|
23
|
+
"A rectangular region of a digital image. The value of the qualifier is interpreted as a series "
|
24
|
+
"of four comma-separated numbers. If all numbers are less than 1, it is interpreted as x1,y1,x2,y2, "
|
25
|
+
"representing percentage-based coordinates of the top-left and bottom-right corners. If any number is "
|
26
|
+
"more than 1, it is interpreted as x,y,w,h where x and y are coordinates in pixels, and w and h are "
|
27
|
+
"the width and height of the rectangle in pixels."
|
28
|
+
),
|
29
|
+
self.TimeRegion: (
|
30
|
+
"A region of time in a digital audio or video recording, in the form of a,b where a is the starting "
|
31
|
+
"point in milliseconds and b is the ending point in milliseconds. This qualifier's meaning is undefined "
|
32
|
+
"if the source is not a digital audio or video recording."
|
33
|
+
),
|
34
|
+
self.Page: (
|
35
|
+
"A single page in a multi-page document, represented as a 1-based integer. This always references the "
|
36
|
+
"absolute page number, not any custom page number. This qualifier is undefined if the source is not a "
|
37
|
+
"multi-page document."
|
38
|
+
)
|
39
|
+
}
|
40
|
+
return descriptions.get(self, "No description available.")
|
41
|
+
|
42
|
+
class SourceReference:
|
43
|
+
identifier = 'http://gedcomx.org/v1/SourceReference'
|
44
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
45
|
+
|
46
|
+
def __init__(self,
|
47
|
+
description: URI, descriptionId: Optional[str] = None, attribution: Optional[Attribution] = None, qualifiers: Optional[List[Qualifier]] = []) -> None:
|
48
|
+
|
49
|
+
#if not isinstance(description,URI): raise ValueError(f"description is of type {type(description)}")
|
50
|
+
|
51
|
+
from .SourceDescription import SourceDescription
|
52
|
+
self._description_object = None
|
53
|
+
if isinstance(description,URI):
|
54
|
+
#TODO See if Local, If not try to resolve,
|
55
|
+
self._description_object = description
|
56
|
+
|
57
|
+
elif isinstance(description,SourceDescription):
|
58
|
+
self._description_object = description
|
59
|
+
if hasattr(description,'_uri'):
|
60
|
+
self.description = description._uri
|
61
|
+
else:
|
62
|
+
assert False
|
63
|
+
self.description = URI(object=description)
|
64
|
+
description._uri = self.description
|
65
|
+
description._object = description
|
66
|
+
else:
|
67
|
+
raise ValueError(f"'description' must be of type 'SourceDescription' or 'URI', type: {type(description)} was provided")
|
68
|
+
|
69
|
+
#self.description = description
|
70
|
+
self.descriptionId = descriptionId
|
71
|
+
self.attribution = attribution
|
72
|
+
self.qualifiers = qualifiers
|
73
|
+
|
74
|
+
def add_qualifier(self, qualifier: Qualifier):
|
75
|
+
if isinstance(qualifier, Qualifier):
|
76
|
+
self.qualifiers.append(qualifier)
|
77
|
+
|
78
|
+
def append(self, text_to_add: str):
|
79
|
+
if text_to_add and isinstance(text_to_add, str):
|
80
|
+
if self.descriptionId is None:
|
81
|
+
self.descriptionId = text_to_add
|
82
|
+
else:
|
83
|
+
self.descriptionId += text_to_add
|
84
|
+
else:
|
85
|
+
raise ValueError("The 'text_to_add' must be a non-empty string.")
|
86
|
+
|
87
|
+
@property
|
88
|
+
def _as_dict_(self):
|
89
|
+
|
90
|
+
def _serialize(value):
|
91
|
+
if isinstance(value, (str, int, float, bool, type(None))):
|
92
|
+
return value
|
93
|
+
elif isinstance(value, dict):
|
94
|
+
return {k: _serialize(v) for k, v in value.items()}
|
95
|
+
elif isinstance(value, (list, tuple, set)):
|
96
|
+
return [_serialize(v) for v in value]
|
97
|
+
elif hasattr(value, "_as_dict_"):
|
98
|
+
return value._as_dict_
|
99
|
+
else:
|
100
|
+
return str(value) # fallback for unknown objects
|
101
|
+
|
102
|
+
# Only add Relationship-specific fields
|
103
|
+
sourcereference_fields = {
|
104
|
+
'description':self._description_object._uri if self._description_object else None,
|
105
|
+
'descriptionId': self.descriptionId.replace("\n"," ").replace("\r"," ") if self.descriptionId else None,
|
106
|
+
'attribution': self.attribution if self.attribution else None,
|
107
|
+
'qualifiers':[qualifier.value for qualifier in self.qualifiers ] if self.qualifiers else None
|
108
|
+
}
|
109
|
+
|
110
|
+
# Serialize and exclude None values
|
111
|
+
for key, value in sourcereference_fields.items():
|
112
|
+
if value is not None:
|
113
|
+
sourcereference_fields[key] = _serialize(value)
|
114
|
+
|
115
|
+
return sourcereference_fields
|
116
|
+
|
117
|
+
@classmethod
|
118
|
+
def _from_json_(cls, data: dict):
|
119
|
+
print(data)
|
120
|
+
|
121
|
+
"""
|
122
|
+
Rehydrate a SourceReference from the dict form produced by _as_dict_.
|
123
|
+
"""
|
124
|
+
from .SourceDescription import SourceDescription
|
125
|
+
|
126
|
+
# 1) Reconstruct the SourceDescription object
|
127
|
+
desc_json = data.get('description')
|
128
|
+
#if not desc_json:
|
129
|
+
# raise ValueError("SourceReference JSON missing 'description'")
|
130
|
+
#desc_obj = SourceDescription._from_json_(desc_json)
|
131
|
+
desc_obj = URI.from_url(data.get('description')) #TODO <--- URI Reference
|
132
|
+
|
133
|
+
|
134
|
+
# 2) Simple fields
|
135
|
+
description_id = data.get('descriptionId')
|
136
|
+
|
137
|
+
# 3) Attribution (if present)
|
138
|
+
attrib = None
|
139
|
+
if data.get('attribution') is not None:
|
140
|
+
attrib = Attribution._from_json_(data['attribution'])
|
141
|
+
|
142
|
+
# 4) Qualifiers list
|
143
|
+
raw_quals = data.get('qualifiers', [])
|
144
|
+
qualifiers: List[Qualifier] = []
|
145
|
+
for q in raw_quals:
|
146
|
+
try:
|
147
|
+
# Try the known‐source enum first
|
148
|
+
qualifiers.append(KnownSourceReference(q))
|
149
|
+
except ValueError:
|
150
|
+
# Fallback to generic Qualifier
|
151
|
+
qualifiers.append(Qualifier(q))
|
152
|
+
|
153
|
+
# 5) Instantiate via your existing __init__
|
154
|
+
inst = cls(
|
155
|
+
description=desc_obj,
|
156
|
+
descriptionId=description_id,
|
157
|
+
attribution=attrib,
|
158
|
+
qualifiers=qualifiers
|
159
|
+
)
|
160
|
+
return inst
|
161
|
+
|
162
|
+
def __eq__(self, other):
|
163
|
+
if not isinstance(other, self.__class__):
|
164
|
+
return False
|
165
|
+
|
166
|
+
return (
|
167
|
+
self.description._uri == other.description._uri
|
168
|
+
)
|
gedcomx/Subject.py
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
import warnings
|
2
|
+
from typing import List, Optional
|
3
|
+
|
4
|
+
from .Attribution import Attribution
|
5
|
+
from .Conclusion import Conclusion, ConfidenceLevel
|
6
|
+
from .EvidenceReference import EvidenceReference
|
7
|
+
from .Identifier import Identifier
|
8
|
+
from .Note import Note
|
9
|
+
from .Serialization import serialize_to_dict
|
10
|
+
from .SourceReference import SourceReference
|
11
|
+
from .URI import URI
|
12
|
+
|
13
|
+
class Subject(Conclusion):
|
14
|
+
identifier = 'http://gedcomx.org/v1/Subject'
|
15
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
16
|
+
|
17
|
+
def __init__(self,
|
18
|
+
id: Optional[str],
|
19
|
+
lang: Optional[str] = 'en',
|
20
|
+
sources: Optional[List[SourceReference]] = [],
|
21
|
+
analysis: Optional[URI] = None,
|
22
|
+
notes: Optional[List[Note]] = [],
|
23
|
+
confidence: Optional[ConfidenceLevel] = None,
|
24
|
+
attribution: Optional[Attribution] = None,
|
25
|
+
extracted: Optional[bool] = None,
|
26
|
+
evidence: Optional[List[EvidenceReference]] = [],
|
27
|
+
media: Optional[List[SourceReference]] = [],
|
28
|
+
identifiers: Optional[List[Identifier]] = [],
|
29
|
+
uri: Optional[URI] = None) -> None:
|
30
|
+
super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
|
31
|
+
self.extracted = extracted
|
32
|
+
self.evidence = evidence
|
33
|
+
self.media = media
|
34
|
+
self.identifiers = identifiers
|
35
|
+
|
36
|
+
|
37
|
+
def add_identifier(self, identifier_to_add: Identifier):
|
38
|
+
if identifier_to_add and isinstance(identifier_to_add,Identifier):
|
39
|
+
for current_identifier in self.identifiers:
|
40
|
+
if identifier_to_add == current_identifier:
|
41
|
+
return
|
42
|
+
self.identifiers.append(identifier_to_add)
|
43
|
+
|
44
|
+
@property
|
45
|
+
def _as_dict_(self):
|
46
|
+
def _serialize(value):
|
47
|
+
if isinstance(value, (str, int, float, bool, type(None))):
|
48
|
+
return value
|
49
|
+
elif isinstance(value, dict):
|
50
|
+
return {k: _serialize(v) for k, v in value.items()}
|
51
|
+
elif isinstance(value, (list, tuple, set)):
|
52
|
+
return [_serialize(v) for v in value]
|
53
|
+
elif hasattr(value, "_as_dict_"):
|
54
|
+
return value._as_dict_
|
55
|
+
else:
|
56
|
+
return str(value) # fallback for unknown objects
|
57
|
+
|
58
|
+
subject_fields = super()._as_dict_ # Start with base class fields
|
59
|
+
# Only add Relationship-specific fields
|
60
|
+
subject_fields.update({
|
61
|
+
"extracted": self.extracted,
|
62
|
+
"evidence": [evidence_ref for evidence_ref in self.evidence] if self.evidence else None,
|
63
|
+
"media": [media for media in self.media] if self.media else None,
|
64
|
+
"identifiers": [identifier for identifier in self.identifiers] if self.identifiers else None
|
65
|
+
|
66
|
+
})
|
67
|
+
|
68
|
+
# Serialize and exclude None values
|
69
|
+
for key, value in subject_fields.items():
|
70
|
+
if value is not None:
|
71
|
+
subject_fields[key] = _serialize(value)
|
72
|
+
|
73
|
+
return subject_fields
|
gedcomx/TextValue.py
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
class TextValue:
|
4
|
+
identifier = 'http://gedcomx.org/v1/TextValue'
|
5
|
+
version = 'http://gedcomx.org/conceptual-model/v1'
|
6
|
+
|
7
|
+
def __init__(self, value: Optional[str] = None, lang: Optional[str] = 'en') -> None:
|
8
|
+
self.lang = lang
|
9
|
+
self.value = value
|
10
|
+
|
11
|
+
def _append_to_value(self, value_to_append):
|
12
|
+
if not isinstance(value_to_append, str):
|
13
|
+
raise ValueError(f"Cannot append object of type {type(value_to_append)}.")
|
14
|
+
self.value += ' ' + value_to_append
|
15
|
+
|
16
|
+
def _prop_dict(self):
|
17
|
+
return {
|
18
|
+
"lang":self.lang if self.lang else None,
|
19
|
+
"value":self.value if self.value else None
|
20
|
+
}
|
21
|
+
|
22
|
+
def __str__(self):
|
23
|
+
return f"{self.value} ({self.lang})"
|
24
|
+
|
25
|
+
# ...existing code...
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def _from_json_(cls, data: dict):
|
29
|
+
"""
|
30
|
+
Create a TextValue instance from a JSON-dict (already parsed).
|
31
|
+
"""
|
32
|
+
value = data.get('value')
|
33
|
+
lang = data.get('lang', 'en')
|
34
|
+
return cls(value=value, lang=lang)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from .TextValue import TextValue
|
2
|
+
|
3
|
+
class TopLevelTypeCollection:
|
4
|
+
def __init__(self):
|
5
|
+
self.items = {}
|
6
|
+
self.items_by_type = {}
|
7
|
+
self._id_idx ={} # Hash Table for searching for item by id
|
8
|
+
self._name_idx = {}
|
9
|
+
self._uri_idx = {}
|
10
|
+
self.authority = 'NewGedcomX'
|
11
|
+
self.len = 0
|
12
|
+
|
13
|
+
|
14
|
+
def append(self, item) -> bool:
|
15
|
+
if item._uri._authority == '' or item._uri._authority is None:
|
16
|
+
item._uri._authority = self.authority
|
17
|
+
if item._uri._path == '' or item._uri._path is None:
|
18
|
+
item._uri._path = f'{item.__class__.__name__}s'
|
19
|
+
|
20
|
+
self.items[item._uri] = item
|
21
|
+
self._update_indexes(item)
|
22
|
+
self.len += 1
|
23
|
+
|
24
|
+
def _update_indexes(self, item):
|
25
|
+
# Update the id index
|
26
|
+
if hasattr(item, 'id'):
|
27
|
+
self._id_idx[item.id] = item
|
28
|
+
if hasattr(item, 'names'):
|
29
|
+
for name in item.names:
|
30
|
+
if isinstance(name,TextValue):
|
31
|
+
self._name_idx[name] = item
|
32
|
+
else:
|
33
|
+
break
|
34
|
+
if item.__class__.__name__ in self.items_by_type.keys():
|
35
|
+
self.items_by_type[item.__class__.__name__].append(item)
|
36
|
+
else:
|
37
|
+
self.items_by_type[item.__class__.__name__] = [item]
|
38
|
+
|
39
|
+
|
40
|
+
def _dump_uris(self):
|
41
|
+
for uri in self.items.keys():
|
42
|
+
print(uri._uri)
|
43
|
+
|
44
|
+
def __len__(self):
|
45
|
+
return self.len
|
46
|
+
|
47
|
+
|
gedcomx/URI.py
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
from urllib.parse import urlparse
|
3
|
+
|
4
|
+
class URI:
|
5
|
+
@classmethod
|
6
|
+
def from_url(cls, url: str) -> 'URI':
|
7
|
+
parsed_url = urlparse(url)
|
8
|
+
return cls(
|
9
|
+
value=url,
|
10
|
+
scheme=parsed_url.scheme if parsed_url.scheme else 'gedcomx',
|
11
|
+
authority=parsed_url.netloc,
|
12
|
+
path=parsed_url.path,
|
13
|
+
query=parsed_url.query,
|
14
|
+
fragment=parsed_url.fragment
|
15
|
+
)
|
16
|
+
|
17
|
+
def parse(value: str) -> None:
|
18
|
+
"""Parse the URI string and populate attributes."""
|
19
|
+
parsed = urlparse(value)
|
20
|
+
return URI(scheme=parsed.scheme,authority = parsed.netloc, path = parsed.path, query = parsed.query, fragment = parsed.fragment)
|
21
|
+
|
22
|
+
|
23
|
+
def __init__(self, value: Optional[str] = None,
|
24
|
+
scheme: Optional[str] = None,
|
25
|
+
authority: Optional[str] = None,
|
26
|
+
path: Optional[str] = None,
|
27
|
+
query: Optional[str] = None,
|
28
|
+
fragment: Optional[str] = None,
|
29
|
+
object = None) -> None:
|
30
|
+
|
31
|
+
self._scheme = scheme if scheme else 'gedcomx'
|
32
|
+
self._authority = authority
|
33
|
+
self._path = path
|
34
|
+
self._query = query
|
35
|
+
self._fragment = fragment
|
36
|
+
self._object = None
|
37
|
+
|
38
|
+
if object is not None:
|
39
|
+
self._object = object
|
40
|
+
if hasattr(object,'_uri'):
|
41
|
+
self._object._uri = self
|
42
|
+
|
43
|
+
|
44
|
+
@property
|
45
|
+
def _uri(self) -> str:
|
46
|
+
uri = ""
|
47
|
+
if self._scheme:
|
48
|
+
uri += f"{self._scheme}://"
|
49
|
+
if self._authority:
|
50
|
+
uri += f"{self._authority}/"
|
51
|
+
if self._path:
|
52
|
+
uri += f"{self._path}/"
|
53
|
+
if self._query:
|
54
|
+
uri += f"?{self._query}"
|
55
|
+
if self._fragment:
|
56
|
+
uri += f"#{self._fragment}"
|
57
|
+
return uri
|
58
|
+
|
59
|
+
@property
|
60
|
+
def value(self):
|
61
|
+
return self._uri
|
62
|
+
|
63
|
+
@property
|
64
|
+
def _as_dict_(self):
|
65
|
+
return {"Resource":self._uri}
|
66
|
+
|
67
|
+
|
68
|
+
@classmethod
|
69
|
+
def _from_json_(obj,text):
|
70
|
+
return URI(scheme='NEED TO DEAL WITH URI')
|
gedcomx/_Resource.py
ADDED
gedcomx/__init__.py
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
from .Agent import Agent
|
2
|
+
from .Address import Address
|
3
|
+
from .Attribution import Attribution
|
4
|
+
from .Conclusion import Conclusion
|
5
|
+
from .Coverage import Coverage
|
6
|
+
from .Date import Date
|
7
|
+
from .Document import Document
|
8
|
+
from .Document import DocumentType
|
9
|
+
from .EvidenceReference import EvidenceReference
|
10
|
+
from .Event import Event
|
11
|
+
from .Event import EventType
|
12
|
+
from .Event import EventRole
|
13
|
+
from .Fact import Fact
|
14
|
+
from .Fact import FactQualifier
|
15
|
+
from .Fact import FactType
|
16
|
+
from .Gedcom import Gedcom
|
17
|
+
from .GedcomX import GedcomX, Translater
|
18
|
+
from .Gender import Gender, GenderType
|
19
|
+
from .Group import Group, GroupRole
|
20
|
+
from .Identifier import Identifier, IdentifierType
|
21
|
+
from .Name import Name, NameForm, NamePart, NamePartType, NameType, NamePartQualifier
|
22
|
+
from .Note import Note
|
23
|
+
from .OnlineAccount import OnlineAccount
|
24
|
+
from .Person import Person
|
25
|
+
from .PlaceDescription import PlaceDescription
|
26
|
+
from .PlaceReference import PlaceReference
|
27
|
+
from .Qualifier import Qualifier
|
28
|
+
from .Relationship import Relationship, RelationshipType
|
29
|
+
from .Serialization import serialize_to_dict
|
30
|
+
from .SourceCitation import SourceCitation
|
31
|
+
from .SourceDescription import SourceDescription
|
32
|
+
from .SourceDescription import ResourceType
|
33
|
+
from .SourceReference import SourceReference
|
34
|
+
from .Subject import Subject
|
35
|
+
from .TextValue import TextValue
|
36
|
+
from .URI import URI
|
37
|
+
|
38
|
+
|
39
|
+
|