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.
@@ -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
@@ -0,0 +1,11 @@
1
+ from typing import Any
2
+ from .URI import URI
3
+
4
+ class Resource:
5
+ def __init__(self, obj: Any):
6
+ if isinstance(obj,URI):
7
+ pass
8
+ elif hasattr(obj,'_uri'):
9
+ pass
10
+ else:
11
+ return None
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
+