gedcom7 0.2.0__tar.gz → 0.3.0__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.
- {gedcom7-0.2.0 → gedcom7-0.3.0}/PKG-INFO +4 -2
- {gedcom7-0.2.0 → gedcom7-0.3.0}/gedcom7/__init__.py +1 -1
- gedcom7-0.3.0/gedcom7/cast.py +196 -0
- gedcom7-0.3.0/gedcom7/const.py +1941 -0
- gedcom7-0.3.0/gedcom7/grammar.py +118 -0
- {gedcom7-0.2.0 → gedcom7-0.3.0}/gedcom7/parser.py +9 -7
- gedcom7-0.3.0/gedcom7/types.py +140 -0
- gedcom7-0.3.0/gedcom7/util.py +60 -0
- {gedcom7-0.2.0 → gedcom7-0.3.0}/gedcom7.egg-info/PKG-INFO +4 -2
- {gedcom7-0.2.0 → gedcom7-0.3.0}/gedcom7.egg-info/SOURCES.txt +4 -1
- {gedcom7-0.2.0 → gedcom7-0.3.0}/setup.cfg +1 -0
- gedcom7-0.3.0/test/test_cast.py +338 -0
- {gedcom7-0.2.0 → gedcom7-0.3.0}/test/test_parser.py +1 -6
- gedcom7-0.3.0/test/test_util.py +204 -0
- gedcom7-0.2.0/gedcom7/const.py +0 -143
- gedcom7-0.2.0/gedcom7/grammar.py +0 -77
- gedcom7-0.2.0/gedcom7/types.py +0 -162
- gedcom7-0.2.0/test/test_types.py +0 -98
- {gedcom7-0.2.0 → gedcom7-0.3.0}/LICENSE +0 -0
- {gedcom7-0.2.0 → gedcom7-0.3.0}/README.md +0 -0
- {gedcom7-0.2.0 → gedcom7-0.3.0}/gedcom7.egg-info/dependency_links.txt +0 -0
- {gedcom7-0.2.0 → gedcom7-0.3.0}/gedcom7.egg-info/top_level.txt +0 -0
- {gedcom7-0.2.0 → gedcom7-0.3.0}/pyproject.toml +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: gedcom7
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: GEDCOM 7 parser
|
|
5
|
+
Home-page: https://github.com/DavidMStraub/python-gedcom7
|
|
5
6
|
Author: David M. Straub
|
|
6
7
|
Author-email: straub@protonmail.com
|
|
7
8
|
License: MIT
|
|
8
9
|
Description-Content-Type: text/markdown
|
|
9
10
|
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
10
12
|
|
|
11
13
|
# gedcom7
|
|
12
14
|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Cast strings values to the appropriate data type."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
|
|
9
|
+
from . import const, grammar, types
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def cast_value(text: str, type_id: str) -> types.DataType | None:
|
|
15
|
+
if not text:
|
|
16
|
+
return None
|
|
17
|
+
payload = const.payloads.get(type_id)
|
|
18
|
+
if not payload:
|
|
19
|
+
logger.warning("Encountered unknown data type %s", type_id)
|
|
20
|
+
return None
|
|
21
|
+
cast_fuction = CAST_FUNCTIONS.get(payload)
|
|
22
|
+
if not cast_fuction:
|
|
23
|
+
return text
|
|
24
|
+
return cast_fuction(text)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _cast_bool(value: str) -> bool:
|
|
28
|
+
"""Cast a string to a boolean."""
|
|
29
|
+
if value == "Y":
|
|
30
|
+
return True
|
|
31
|
+
if not value:
|
|
32
|
+
return False
|
|
33
|
+
else:
|
|
34
|
+
raise ValueError(f"Cannot interpret {value} as boolean")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _cast_integer(value: str) -> int:
|
|
38
|
+
"""Cast a string to an integer."""
|
|
39
|
+
try:
|
|
40
|
+
return int(value)
|
|
41
|
+
except ValueError:
|
|
42
|
+
raise ValueError(f"Cannot interpret {value} as integer")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cast_list_text(value: str) -> list[str]:
|
|
46
|
+
"""Cast a string to a list of strings."""
|
|
47
|
+
return [el.strip() for el in value.split(",")]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _match(text: str, regex: str, type_name: str) -> re.Match:
|
|
51
|
+
"""Match a string and raise if not compatible."""
|
|
52
|
+
match = re.fullmatch(regex, text)
|
|
53
|
+
if not match:
|
|
54
|
+
raise ValueError(f"Cannot interpret {text} as type {type_name}")
|
|
55
|
+
return match
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _cast_personal_name(value: str) -> types.PersonalName:
|
|
59
|
+
"""Cast a string to a PersonalName."""
|
|
60
|
+
match = _match(value, grammar.personalname, "PersonalName")
|
|
61
|
+
return types.PersonalName(
|
|
62
|
+
fullname=value.replace("/", ""),
|
|
63
|
+
surname=match.group("surname").strip() if match.group("surname") else None,
|
|
64
|
+
given=match.group("given").strip() if match.group("given") else None,
|
|
65
|
+
suffix=match.group("suffix").strip() if match.group("suffix") else None,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _cast_time(value: str) -> types.Time:
|
|
70
|
+
"""Cast a string to a Time."""
|
|
71
|
+
match = _match(value, grammar.time, "Time")
|
|
72
|
+
return types.Time(
|
|
73
|
+
tz=match.group("tz"),
|
|
74
|
+
hour=int(match.group("hour")),
|
|
75
|
+
minute=int(match.group("minute")),
|
|
76
|
+
second=int(match.group("second")) if match.group("second") else None,
|
|
77
|
+
fraction=int(match.group("fraction")) if match.group("fraction") else None,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _cast_age(value: str) -> types.Age:
|
|
82
|
+
"""Cast a string to an Age."""
|
|
83
|
+
match = _match(value, grammar.age, "Age")
|
|
84
|
+
res = {
|
|
85
|
+
"agebound": match.group("agebound"),
|
|
86
|
+
"years": match.group("years"),
|
|
87
|
+
"months": match.group("months1") or match.group("months2"),
|
|
88
|
+
"weeks": match.group("weeks1")
|
|
89
|
+
or match.group("weeks2")
|
|
90
|
+
or match.group("weeks3"),
|
|
91
|
+
"days": match.group("days1")
|
|
92
|
+
or match.group("days2")
|
|
93
|
+
or match.group("days3")
|
|
94
|
+
or match.group("days4"),
|
|
95
|
+
}
|
|
96
|
+
return types.Age(
|
|
97
|
+
agebound=res["agebound"],
|
|
98
|
+
years=int(res["years"].rstrip("y")) if res["years"] else None,
|
|
99
|
+
months=int(res["months"].rstrip("m")) if res["months"] else None,
|
|
100
|
+
weeks=int(res["weeks"].rstrip("w")) if res["weeks"] else None,
|
|
101
|
+
days=int(res["days"].rstrip("d")) if res["days"] else None,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _cast_enum(value: str) -> str:
|
|
106
|
+
"""Cast a string to an Enum."""
|
|
107
|
+
match = _match(value, grammar.enum, "Enum")
|
|
108
|
+
return match.group(0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _cast_list_enum(value: str) -> list[str]:
|
|
112
|
+
"""Cast a string to a list of Enums."""
|
|
113
|
+
match = _match(value, grammar.list_enum, "ListEnum")
|
|
114
|
+
return [el.strip() for el in match.group(0).split(",")]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _cast_mediatype(value: str) -> types.MediaType:
|
|
118
|
+
"""Cast a string to a MediaType."""
|
|
119
|
+
match = _match(value, grammar.mediatype, "MediaType")
|
|
120
|
+
return types.MediaType(media_type=match.group(0))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _cast_date_exact(value: str) -> types.DateExact:
|
|
124
|
+
"""Cast a string to a DateExact."""
|
|
125
|
+
match = _match(value, grammar.dateexact, "DateExact")
|
|
126
|
+
return types.DateExact(
|
|
127
|
+
day=int(match.group("day")),
|
|
128
|
+
month=match.group("month"),
|
|
129
|
+
year=int(match.group("year")),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _cast_date(value: str) -> types.Date:
|
|
134
|
+
"""Cast a string to a Date."""
|
|
135
|
+
match = _match(value, grammar.date_capture, "Date")
|
|
136
|
+
return types.Date(
|
|
137
|
+
calendar=match.group("calendar"),
|
|
138
|
+
day=int(match.group("day")) if match.group("day") else None,
|
|
139
|
+
month=match.group("month"),
|
|
140
|
+
year=int(match.group("year")),
|
|
141
|
+
epoch=match.group("epoch"),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _cast_date_period(value: str) -> types.DatePeriod:
|
|
146
|
+
"""Cast a string to a DatePeriod."""
|
|
147
|
+
match = _match(value, grammar.dateperiod, "DatePeriod")
|
|
148
|
+
res = {}
|
|
149
|
+
if match.group("todate1") or match.group("todate2"):
|
|
150
|
+
to_date = match.group("todate1") or match.group("todate2")
|
|
151
|
+
to_date_match = re.fullmatch(grammar.date_capture, to_date)
|
|
152
|
+
if to_date_match:
|
|
153
|
+
res["to"] = types.Date(
|
|
154
|
+
calendar=to_date_match.group("calendar"),
|
|
155
|
+
day=(
|
|
156
|
+
int(to_date_match.group("day"))
|
|
157
|
+
if to_date_match.group("day")
|
|
158
|
+
else None
|
|
159
|
+
),
|
|
160
|
+
month=to_date_match.group("month"),
|
|
161
|
+
year=int(to_date_match.group("year")),
|
|
162
|
+
epoch=to_date_match.group("epoch"),
|
|
163
|
+
)
|
|
164
|
+
if match.group("fromdate"):
|
|
165
|
+
from_date_match = re.fullmatch(grammar.date_capture, match.group("fromdate"))
|
|
166
|
+
if from_date_match:
|
|
167
|
+
res["from_"] = types.Date(
|
|
168
|
+
calendar=from_date_match.group("calendar"),
|
|
169
|
+
day=(
|
|
170
|
+
int(from_date_match.group("day"))
|
|
171
|
+
if from_date_match.group("day")
|
|
172
|
+
else None
|
|
173
|
+
),
|
|
174
|
+
month=from_date_match.group("month"),
|
|
175
|
+
year=int(from_date_match.group("year")),
|
|
176
|
+
epoch=from_date_match.group("epoch"),
|
|
177
|
+
)
|
|
178
|
+
return types.DatePeriod(**res)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
CAST_FUNCTIONS: dict[str, Callable[[str], types.DataType] | None] = {
|
|
182
|
+
"Y|<NULL>": _cast_bool,
|
|
183
|
+
"http://www.w3.org/2001/XMLSchema#Language": None,
|
|
184
|
+
"http://www.w3.org/2001/XMLSchema#nonNegativeInteger": _cast_integer,
|
|
185
|
+
"http://www.w3.org/2001/XMLSchema#string": None,
|
|
186
|
+
"http://www.w3.org/ns/dcat#mediaType": _cast_mediatype,
|
|
187
|
+
"https://gedcom.io/terms/v7/type-Age": _cast_age,
|
|
188
|
+
"https://gedcom.io/terms/v7/type-Date": _cast_date,
|
|
189
|
+
"https://gedcom.io/terms/v7/type-Date#exact": _cast_date_exact,
|
|
190
|
+
"https://gedcom.io/terms/v7/type-Date#period": _cast_date_period,
|
|
191
|
+
"https://gedcom.io/terms/v7/type-Enum": _cast_enum,
|
|
192
|
+
"https://gedcom.io/terms/v7/type-List#Enum": _cast_list_enum,
|
|
193
|
+
"https://gedcom.io/terms/v7/type-List#Text": _cast_list_text,
|
|
194
|
+
"https://gedcom.io/terms/v7/type-Name": _cast_personal_name,
|
|
195
|
+
"https://gedcom.io/terms/v7/type-Time": _cast_time,
|
|
196
|
+
}
|