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.
gedcomx/GedcomX.py ADDED
@@ -0,0 +1,1105 @@
1
+ DEBUG = False
2
+ import base64
3
+ import json
4
+ import mimetypes
5
+ import re
6
+ import uuid
7
+ import xml.etree.ElementTree as ET
8
+
9
+ from typing import List, Optional
10
+ from xml.dom import minidom
11
+ from .Address import Address
12
+ from .Agent import Agent
13
+ from .Attribution import Attribution
14
+ from .Conclusion import Conclusion
15
+ from .Coverage import Coverage
16
+ from .Date import Date
17
+ from .Document import Document
18
+ from .EvidenceReference import EvidenceReference
19
+ from .Event import Event,EventType,EventRole,EventRoleType
20
+ from .Fact import Fact, FactType, FactQualifier
21
+ from .Gedcom import Gedcom
22
+ from .Gedcom import GedcomRecord
23
+ from .Gender import Gender, GenderType
24
+ from .Group import Group
25
+ from .Identifier import Identifier, IdentifierType
26
+ from .Name import Name, NameType, NameForm, NamePart, NamePartType, NamePartQualifier
27
+ from .Note import Note
28
+ from .OnlineAccount import OnlineAccount
29
+ from .Person import Person
30
+ from .PlaceDescription import PlaceDescription
31
+ from .PlaceReference import PlaceReference
32
+ from .Qualifier import Qualifier
33
+ from .Relationship import Relationship, RelationshipType
34
+ from .SourceCitation import SourceCitation
35
+ from .SourceDescription import SourceDescription, ResourceType
36
+ from .SourceReference import SourceReference, KnownSourceReference
37
+ from .Subject import Subject
38
+ from .TextValue import TextValue
39
+ from .TopLevelTypeCollection import TopLevelTypeCollection
40
+ from .URI import URI
41
+
42
+ def TypeCollection(item_type):
43
+ class Collection:
44
+ def __init__(self):
45
+ self._items = []
46
+ self._id_index = {}
47
+ self._name_index = {}
48
+ self._uri_index = {}
49
+ self.uri = URI(path=f'/{item_type.__name__}s/')
50
+
51
+ def __iter__(self):
52
+ self._index = 0
53
+ return self
54
+
55
+ def __next__(self):
56
+ if self._index < len(self._items):
57
+ result = self._items[self._index]
58
+ self._index += 1
59
+ return result
60
+ else:
61
+ raise StopIteration
62
+
63
+ @property
64
+ def item_type(self):
65
+ return item_type
66
+
67
+ def _update_indexes(self, item):
68
+ # Update the id index
69
+ if hasattr(item, 'id'):
70
+ self._id_index[item.id] = item
71
+
72
+ try:
73
+ if hasattr(item, '_uri'):
74
+ self._uri_index[item._uri.value] = item
75
+ except AttributeError as e:
76
+ print(f"type{item}")
77
+ assert False
78
+
79
+ # Update the name index
80
+ ''' #TODO Fix name handling on persons
81
+ if hasattr(item, 'names'):
82
+ names = getattr(item, 'names')
83
+ for name in names:
84
+ print(name._as_dict_)
85
+ name_value = name.value if isinstance(name, TextValue) else name
86
+ if name_value in self._name_index:
87
+ self._name_index[name_value].append(item)
88
+ else:
89
+ self._name_index[name_value] = [item]
90
+ '''
91
+
92
+ def _remove_from_indexes(self, item):
93
+ # Remove from the id index
94
+ if hasattr(item, 'id'):
95
+ if item.id in self._id_index:
96
+ del self._id_index[item.id]
97
+
98
+ # Remove from the name index
99
+ if hasattr(item, 'names'):
100
+ names = getattr(item, 'names')
101
+ for name in names:
102
+ name_value = name.value if isinstance(name, TextValue) else name
103
+ if name_value in self._name_index:
104
+ if item in self._name_index[name_value]:
105
+ self._name_index[name_value].remove(item)
106
+ if not self._name_index[name_value]:
107
+ del self._name_index[name_value]
108
+
109
+ def byName(self, sname: str):
110
+ # Use the name index for fast lookup
111
+ sname = sname.strip()
112
+ return self._name_index.get(sname, [])
113
+
114
+ def byId(self, id):
115
+ # Use the id index for fast lookup
116
+ return self._id_index.get(id, None)
117
+
118
+ def byUri(self, uri):
119
+ # Use the id index for fast lookup
120
+ return self._uri_index.get(uri.value, None)
121
+
122
+ def append(self, item):
123
+ if not isinstance(item, item_type):
124
+ raise TypeError(f"Expected item of type {item_type.__name__}, got {type(item).__name__}")
125
+ item._uri._path = f'{str(item_type.__name__)}s' if item._uri._path is None else item._uri._path
126
+ #if isinstance(item,Agent):
127
+ # item._uri._path = 'Agents'
128
+ # print(item._uri._as_dict_)
129
+
130
+ self._items.append(item)
131
+ self._update_indexes(item)
132
+
133
+ def remove(self, item):
134
+ if item not in self._items:
135
+ raise ValueError("Item not found in the collection.")
136
+ self._items.remove(item)
137
+ self._remove_from_indexes(item)
138
+
139
+ def __repr__(self):
140
+ return f"Collection({self._items!r})"
141
+
142
+ def list(self):
143
+ for item in self._items:
144
+ print(item)
145
+
146
+ def __call__(self, **kwargs):
147
+ results = []
148
+ for item in self._items:
149
+ match = True
150
+ for key, value in kwargs.items():
151
+ if not hasattr(item, key) or getattr(item, key) != value:
152
+ match = False
153
+ break
154
+ if match:
155
+ results.append(item)
156
+ return results
157
+
158
+ def __len__(self):
159
+ return len(self._items)
160
+
161
+ def __getitem__(self, index):
162
+ return self._items[index]
163
+
164
+ @property
165
+ def _items_as_dict(self) -> dict:
166
+ print(self.item_type)
167
+
168
+ #return {f'{str(item_type.__name__)}s': [item._as_dict_ for item in self._items]}
169
+ return {f'{str(item_type.__name__)}s': [item._as_dict_ for item in self._items]}
170
+
171
+ @property
172
+ def json(self):
173
+ return json.dumps(self._as_dict, indent=4)
174
+
175
+ @property
176
+ def _as_dict_(self):
177
+ return {"Collection": [item._as_dict_ for item in self._items]}
178
+
179
+ return Collection()
180
+
181
+ class GedcomX:
182
+ version = 'http://gedcomx.org/conceptual-model/v1'
183
+
184
+ def __init__(self, id: str = None, attribution: Attribution = None, filepath: str = None) -> None:
185
+ self.id = id
186
+ self.attribution = attribution
187
+
188
+ self._filepath = None
189
+
190
+ self.source_descriptions = TypeCollection(SourceDescription)
191
+ self.persons = TypeCollection(Person)
192
+ self.relationships = TypeCollection(Relationship)
193
+ self.agents = TypeCollection(Agent)
194
+ self.events = TypeCollection(Event)
195
+ self.documents = TypeCollection(Document)
196
+ self.places = TypeCollection(PlaceDescription)
197
+ self.groups = TypeCollection(Group)
198
+
199
+ self.person_relationships_lookup = {}
200
+
201
+ def _add(self,gedcomx_type_object):
202
+ #print(type(gedcomx_type_object))
203
+ if gedcomx_type_object:
204
+ if isinstance(gedcomx_type_object,Person):
205
+ self.add_person(gedcomx_type_object)
206
+ elif isinstance(gedcomx_type_object,SourceDescription):
207
+ self.add_source_description(gedcomx_type_object)
208
+ elif isinstance(gedcomx_type_object,Agent):
209
+ self.add_agent(gedcomx_type_object)
210
+ elif isinstance(gedcomx_type_object,PlaceDescription):
211
+ self.add_place_description(gedcomx_type_object)
212
+ elif isinstance(gedcomx_type_object,Event):
213
+ self.add_event(gedcomx_type_object)
214
+ elif isinstance(gedcomx_type_object,Relationship):
215
+ self.add_relationship(gedcomx_type_object)
216
+ else:
217
+ raise ValueError(f"I do not know how to add an Object of type {type(gedcomx_type_object)}")
218
+ else:
219
+ Warning("Tried to add a None type to the Geneology")
220
+
221
+ def add_source_description(self,sourceDescription: SourceDescription):
222
+ if sourceDescription and isinstance(sourceDescription,SourceDescription):
223
+ if sourceDescription.id is None:
224
+ sourceDescription.id =self.default_id_generator()
225
+ self.source_descriptions.append(item=sourceDescription)
226
+ self.lastSourceDescriptionAdded = sourceDescription
227
+ else:
228
+ raise ValueError(f"When adding a SourceDescription, value must be of type SourceDescription, type {type(sourceDescription)} was provided")
229
+
230
+ def add_person(self,person: Person):
231
+ if person and isinstance(person,Person):
232
+ if person.id is None:
233
+ person.id =self.personURIgenerator()
234
+ self.persons.append(item=person)
235
+ else:
236
+ raise ValueError()
237
+
238
+ def add_relationship(self,relationship: Relationship):
239
+ if relationship and isinstance(relationship,Relationship):
240
+ if isinstance(relationship.person1,URI) and isinstance(relationship.person2,URI):
241
+ print("Adding unresolved Relationship")
242
+ self.relationships.append(relationship)
243
+ return
244
+
245
+ if relationship.person1:
246
+ if relationship.person1.id is None:
247
+ relationship.person1.id = self.personURIgenerator()
248
+ if not self.persons.byId(relationship.person1.id):
249
+ self.persons.append(relationship.person1)
250
+ if relationship.person1.id not in self.person_relationships_lookup:
251
+ self.person_relationships_lookup[relationship.person1.id] = []
252
+ self.person_relationships_lookup[relationship.person1.id].append(relationship)
253
+ relationship.person1._add_relationship(relationship)
254
+ else:
255
+ raise ValueError
256
+
257
+ if relationship.person2:
258
+ if relationship.person2.id is None:
259
+ relationship.person2.id = self.personURIgenerator() #TODO
260
+ if not self.persons.byId(relationship.person2.id):
261
+ self.persons.append(relationship.person2)
262
+ if relationship.person2.id not in self.person_relationships_lookup:
263
+ self.person_relationships_lookup[relationship.person2.id] = []
264
+ self.person_relationships_lookup[relationship.person2.id].append(relationship)
265
+ relationship.person2._add_relationship(relationship)
266
+ else:
267
+ raise ValueError
268
+
269
+ self.relationships.append(relationship)
270
+ else:
271
+ raise ValueError()
272
+
273
+ def add_place_description(self,placeDescription: PlaceDescription):
274
+ if placeDescription and isinstance(placeDescription,PlaceDescription):
275
+ if placeDescription.id is None:
276
+ placeDescription.id = self.PlaceDescriptionURIGenerator()
277
+ self.places.append(placeDescription)
278
+
279
+ def add_agent(self,agent: Agent):
280
+ if agent and isinstance(agent,Agent):
281
+ if agent.id is None:
282
+ raise("Empty ID")
283
+ agent.id = Agent.default_id_generator()
284
+ if self.agents.byId(agent.id):
285
+ raise ValueError
286
+ print(f'Added Agent with id: {agent.id}')
287
+ self.agents.append(agent)
288
+
289
+ def add_event(self,event_to_add: Event):
290
+ if event_to_add and isinstance(event_to_add,Event):
291
+ for current_event in self.events:
292
+ if event_to_add == current_event:
293
+ return
294
+ self.events.append(event_to_add)
295
+
296
+ def person(self,id: str):
297
+ filtered = [person for person in self.persons if getattr(person, 'id') == id]
298
+ if filtered: return filtered[0]
299
+ return None
300
+
301
+ def source(self,id: str):
302
+ filtered = [source for source in self.source_descriptions if getattr(source, 'id') == id]
303
+ if filtered: return filtered[0]
304
+ return None
305
+
306
+
307
+ class Translater():
308
+ def __init__(self,gedcom) -> None:
309
+ self.handlers = {}
310
+ self.gedcom: Gedcom = gedcom if gedcom else None
311
+ self.gedcomx = GedcomX()
312
+
313
+ self.object_stack = []
314
+ self.object_map = {}
315
+ self.missing_handler_count = {}
316
+
317
+ self.translate()
318
+
319
+ gedcom_even_to_fact = {
320
+ # Person Fact Types
321
+ "ADOP": FactType.Adoption,
322
+ "CHR": FactType.AdultChristening,
323
+ "EVEN": FactType.Amnesty, # and other FactTypes with no direct GEDCOM tag
324
+ "BAPM": FactType.Baptism,
325
+ "BARM": FactType.BarMitzvah,
326
+ "BASM": FactType.BatMitzvah,
327
+ "BIRT": FactType.Birth,
328
+ "BIRT, CHR": FactType.Birth,
329
+ "BLES": FactType.Blessing,
330
+ "BURI": FactType.Burial,
331
+ "CAST": FactType.Caste,
332
+ "CENS": FactType.Census,
333
+ "CIRC": FactType.Circumcision,
334
+ "CONF": FactType.Confirmation,
335
+ "CREM": FactType.Cremation,
336
+ "DEAT": FactType.Death,
337
+ "EDUC": FactType.Education,
338
+ "EMIG": FactType.Emigration,
339
+ "FCOM": FactType.FirstCommunion,
340
+ "GRAD": FactType.Graduation,
341
+ "IMMI": FactType.Immigration,
342
+ "MIL": FactType.MilitaryService,
343
+ "NATI": FactType.Nationality,
344
+ "NATU": FactType.Naturalization,
345
+ "OCCU": FactType.Occupation,
346
+ "ORDN": FactType.Ordination,
347
+ "DSCR": FactType.PhysicalDescription,
348
+ "PROB": FactType.Probate,
349
+ "PROP": FactType.Property,
350
+ "RELI": FactType.Religion,
351
+ "RESI": FactType.Residence,
352
+ "WILL": FactType.Will,
353
+
354
+ # Couple Relationship Fact Types
355
+ "ANUL": FactType.Annulment,
356
+ "DIV": FactType.Divorce,
357
+ "DIVF": FactType.DivorceFiling,
358
+ "ENGA": FactType.Engagement,
359
+ "MARR": FactType.Marriage,
360
+ "MARB": FactType.MarriageBanns,
361
+ "MARC": FactType.MarriageContract,
362
+ "MARL": FactType.MarriageLicense,
363
+ "SEPA": FactType.Separation,
364
+
365
+ # Parent-Child Relationship Fact Types
366
+ # (Note: Only ADOPTION has a direct GEDCOM tag, others are under "EVEN")
367
+ "ADOP": FactType.AdoptiveParent
368
+ }
369
+
370
+
371
+ def clean_str(text: str) -> str:
372
+ # Regular expression to match HTML/XML tags
373
+ clean_text = re.sub(r'<[^>]+>', '', text)
374
+
375
+ return clean_text
376
+
377
+ def translate(self):
378
+ for repository in self.gedcom.repositories:
379
+ self.parse_record(repository)
380
+ print(f"Translated {len(self.gedcomx.agents)} 'REPO' records to Agents")
381
+ for source in self.gedcom.sources:
382
+ self.parse_record(source)
383
+ print(f"Translated {len(self.gedcomx.source_descriptions)} 'SOUR' records to SourceDescription")
384
+
385
+ for object in self.gedcom.objects:
386
+ self.parse_record(object)
387
+ print(f"Translated {len(self.gedcom.objects)} 'OBJE' records to SourceDescriptions")
388
+
389
+ for individual in self.gedcom.individuals:
390
+ self.parse_record(individual)
391
+ print(f"Translated {len(self.gedcomx.persons)} 'INDI' records to Persons")
392
+
393
+ for key in self.missing_handler_count:
394
+ print(f"{key}: {self.missing_handler_count[key]}")
395
+
396
+
397
+
398
+ fam_count = len(self.gedcom.families)
399
+ for family in self.gedcom.families:
400
+ self.handle_fam(family)
401
+ print(f"Translated {fam_count} 'FAM' records to {len(self.gedcomx.relationships)} Relationship")
402
+
403
+ print(f"Translated {len(self.gedcomx.events)} 'EVEN' records to Events")
404
+
405
+ def find_urls(sef,text: str):
406
+ # Regular expression pattern to match URLs
407
+ url_pattern = re.compile(r'https?://[^\s]+')
408
+ # Find all URLs using the pattern
409
+ urls = url_pattern.findall(text)
410
+ return urls
411
+
412
+ @property
413
+ def event_type_conversion_table(self):
414
+ return {'BIRT':EventType.BIRTH,
415
+ 'OBIT':FactType.OBITUARY}
416
+
417
+ def parse_record(self,record: GedcomRecord):
418
+
419
+ if DEBUG: print(record.describe())
420
+ handler_name = 'handle_' + record.tag.lower()
421
+
422
+ if hasattr(self,handler_name):
423
+
424
+ handler = getattr(self,handler_name)
425
+ handler(record)
426
+ else:
427
+ if record.tag in self.missing_handler_count:
428
+ self.missing_handler_count[record.tag] += 1
429
+ else:
430
+ self.missing_handler_count[record.tag] = 1
431
+ if not record.tag.startswith("_"):
432
+ print(f"\033[1;31mCould not find handler for GEDCOM tag {record.tag}\n{record.describe()}\033[0m")
433
+ for sub_record in record.subRecords():
434
+ self.parse_record(sub_record)
435
+
436
+ def handle__apid(self, record):
437
+ if isinstance(self.object_map[record.level-1], SourceReference):
438
+ self.object_map[record.level-1]._description_object.add_identifier(Identifier(value=URI.from_url('APID://' + record.value)))
439
+ elif isinstance(self.object_map[record.level-1], SourceDescription):
440
+ self.object_map[record.level-1].add_identifier(Identifier(value=URI.from_url('APID://' + record.value)))
441
+ else:
442
+ raise ValueError(f"Could not handle '_APID' tag in record {record.describe()}, last stack object {type(self.object_map[record.level-1])}")
443
+
444
+ def handle__meta(self, record):
445
+ if isinstance(self.object_map[record.level-1], SourceDescription):
446
+ gxobject = Note(text=Translater.clean_str(record.value))
447
+ self.object_map[record.level-1].add_note(gxobject)
448
+ self.object_stack.append(gxobject)
449
+ self.object_map[record.level] = gxobject
450
+ else:
451
+ raise ValueError(f"Could not handle 'WWW' tag in record {record.describe()}, last stack object {self.object_map[record.level-1]}")
452
+
453
+ def handle__wlnk(self, record):
454
+ return self.handle_sour(record)
455
+
456
+ def handle_addr(self, record):
457
+ if isinstance(self.object_map[record.level-1], Agent):
458
+ # TODO CHeck if URL?
459
+ if Translater.clean_str(record.value):
460
+ gxobject = URI(value=Translater.clean_str(record.value))
461
+ else:
462
+ gxobject = Address()
463
+ self.object_map[record.level-1].address = gxobject
464
+ self.object_stack.append(gxobject)
465
+ self.object_map[record.level] = gxobject
466
+ else:
467
+ raise ValueError(f"I do not know how to handle an 'ADDR' tag for a {type(self.object_map[record.level-1])}")
468
+
469
+ def handle_adr1(self, record):
470
+ if isinstance(self.object_map[record.level-1], Address):
471
+ if Translater.clean_str(record.value):
472
+ self.object_map[record.level-1].street = Translater.clean_str(record.value)
473
+
474
+ else:
475
+ raise ValueError(f"I do not know how to handle an 'ADDR' tag for a {type(self.object_map[record.level-1])}")
476
+
477
+ def handle_adop(self, record):
478
+ if isinstance(self.object_map[record.level-1], Person):
479
+ gxobject = Fact(type=FactType.ADOPTION)
480
+ self.object_map[record.level-1].add_fact(gxobject)
481
+
482
+ self.object_stack.append(gxobject)
483
+ self.object_map[record.level] = gxobject
484
+ else:
485
+ print("Cannot translate 'BAPM")
486
+ assert False
487
+
488
+ def handle_auth(self, record):
489
+ if isinstance(self.object_map[record.level-1], SourceDescription):
490
+ if self.gedcomx.agents.byName(record.value):
491
+ gxobject = self.gedcomx.agents.byName(record.value)[0]
492
+ else:
493
+ gxobject = Agent(names=[TextValue(record.value)])
494
+ self.gedcomx.add_agent(gxobject)
495
+
496
+ self.object_map[record.level-1].author = gxobject
497
+ self.object_stack.append(gxobject)
498
+ self.object_map[record.level] = gxobject
499
+ else:
500
+ print("Cannot translate 'AUTH")
501
+ assert False
502
+
503
+ def handle_bapm(self, record):
504
+ if isinstance(self.object_map[record.level-1], Person):
505
+ gxobject = Fact(type=FactType.Baptism)
506
+ self.object_map[record.level-1].add_fact(gxobject)
507
+
508
+ self.object_stack.append(gxobject)
509
+ self.object_map[record.level] = gxobject
510
+ else:
511
+ print("Cannot translate 'BAPM")
512
+ assert False
513
+
514
+ def handle_birt(self, record):
515
+ if isinstance(self.object_map[record.level-1], Person):
516
+ #gxobject = Event(type=EventType.BIRTH, roles=[EventRole(person=self.object_map[record.level-1], type=EventRoleType.Principal)])
517
+ gxobject = Fact(type=FactType.Birth)
518
+ #self.gedcomx.add_event(gxobject)
519
+ self.object_map[record.level-1].add_fact(gxobject)
520
+
521
+ self.object_stack.append(gxobject)
522
+ self.object_map[record.level] = gxobject
523
+ else:
524
+ print("Cannot translate 'AUTH")
525
+ assert False
526
+
527
+ def handle_buri(self, record):
528
+ if isinstance(self.object_map[record.level-1], Person):
529
+ gxobject = Fact(type=FactType.Burial)
530
+ self.object_map[record.level-1].add_fact(gxobject)
531
+
532
+ self.object_stack.append(gxobject)
533
+ self.object_map[record.level] = gxobject
534
+ else:
535
+ print("Cannot translate 'BURI")
536
+ assert False
537
+
538
+ def handle_caln(self, record):
539
+ if isinstance(self.object_map[record.level-1], SourceReference):
540
+ self.object_map[record.level-1].description.add_identifier(Identifier(value=URI.from_url('Call Number:' + record.value)))
541
+ elif isinstance(self.object_map[record.level-1], SourceDescription):
542
+ self.object_map[record.level-1].add_identifier(Identifier(value=URI.from_url('Call Number:' + record.value)))
543
+ elif isinstance(self.object_map[record.level-1], Agent):
544
+ pass
545
+ # TODO Why is GEDCOM so shitty? A callnumber for a repository?
546
+ else:
547
+ raise ValueError(f"Could not handle 'CALN' tag in record {record.describe()}, last stack object {type(self.object_map[record.level-1])}")
548
+
549
+ def handle_chr(self, record):
550
+ if isinstance(self.object_map[record.level-1], Person):
551
+ gxobject = Fact(type=FactType.Christening)
552
+ self.object_map[record.level-1].add_fact(gxobject)
553
+
554
+ self.object_stack.append(gxobject)
555
+ self.object_map[record.level] = gxobject
556
+ else:
557
+ print("Cannot translate 'CHR")
558
+ assert False
559
+
560
+ def handle_city(self, record):
561
+ if isinstance(self.object_map[record.level-1], Address):
562
+ self.object_map[record.level-1].city = Translater.clean_str(record.value)
563
+ else:
564
+ raise ValueError(f"I do not know how to handle an 'CITY' tag for a {type(self.object_map[record.level-1])}")
565
+
566
+ def handle_conc(self, record):
567
+ if isinstance(self.object_map[record.level-1], Note):
568
+ gxobject = Translater.clean_str(str(record.value))
569
+ self.object_map[record.level-1].append(gxobject)
570
+ elif isinstance(self.object_map[record.level-1], Agent):
571
+ gxobject = str(record.value)
572
+ self.object_map[record.level-1]._append_to_name(gxobject)
573
+ elif isinstance(self.object_map[record.level-1], Qualifier):
574
+ gxobject = str(record.value)
575
+ self.object_map[record.level-2].append(gxobject)
576
+ elif isinstance(self.object_map[record.level-1], TextValue):
577
+ #gxobject = TextValue(value=Translater.clean_str(record.value))
578
+ self.object_map[record.level-1]._append_to_value(record.value)
579
+ elif isinstance(self.object_map[record.level-1], SourceReference):
580
+ self.object_map[record.level-1].append(record.value)
581
+ elif isinstance(self.object_map[record.level-1], Fact):
582
+ self.object_map[record.level-1].notes[0].text += record.value
583
+
584
+ else:
585
+ print(f"Cannot translate 'CONC', {self.object_map[record.level-1]}")
586
+ assert False
587
+
588
+ def handle_cont(self, record):
589
+ if isinstance(self.object_map[record.level-1], Note):
590
+ gxobject = str("\n" + record.value)
591
+ self.object_map[record.level-1].append(gxobject)
592
+ elif isinstance(self.object_map[record.level-1], Agent):
593
+ gxobject = str("\n" + record.value)
594
+ elif isinstance(self.object_map[record.level-1], Qualifier):
595
+ gxobject = str("\n" + record.value)
596
+ self.object_map[record.level-1].append(gxobject)
597
+ elif isinstance(self.object_map[record.level-1], TextValue):
598
+ #gxobject = TextValue(value="\n" + record.value)
599
+ self.object_map[record.level-1]._append_to_value(record.value)
600
+ elif isinstance(self.object_map[record.level-1], SourceReference):
601
+ self.object_map[record.level-1].append(record.value)
602
+ else:
603
+ print(f"Cannot translate 'CONT', {type(self.object_map[record.level-1])}")
604
+ assert False
605
+
606
+ def handle__crea(self, record):
607
+ if isinstance(self.object_map[record.level-1], SourceDescription):
608
+ self.object_map[record.level-1].created = Date(original=record.value)
609
+ else:
610
+ raise ValueError(f"Could not handle '_URL' tag in record {record.describe()}, last stack object {self.object_map[record.level-1]}")
611
+
612
+ def handle_ctry(self, record):
613
+ if isinstance(self.object_map[record.level-1], Address):
614
+ self.object_map[record.level-1].country = Translater.clean_str(record.value)
615
+ else:
616
+ raise ValueError(f"I do not know how to handle an 'CTRY' tag for a {type(self.object_map[record.level-1])}")
617
+
618
+ def handle_data(self, record: GedcomRecord) -> None:
619
+ if record.value != '':
620
+ assert False
621
+ self.object_map[record.level] = None
622
+
623
+ def handle_date(self, record: GedcomRecord):
624
+ if record.parent.tag == 'PUBL':
625
+ gxobject = Date(original=record.value)
626
+ self.object_map[0].published = gxobject
627
+
628
+ self.object_stack.append(gxobject)
629
+ self.object_map[record.level] = gxobject
630
+ elif isinstance(self.object_map[record.level-1], Event):
631
+ self.object_map[record.level-1].date = Date(original=record.value)
632
+ elif isinstance(self.object_map[record.level-1], Fact):
633
+ self.object_map[record.level-1].date = Date(original=record.value)
634
+ elif record.parent.tag == 'DATA' and isinstance(self.object_map[record.level-2], SourceReference):
635
+ gxobject = Note(text='Date: ' + record.value)
636
+ self.object_map[record.level-2]._description_object.add_note(gxobject)
637
+ self.object_stack.append(gxobject)
638
+ self.object_map[record.level] = gxobject
639
+ elif isinstance(self.object_map[record.level-1], SourceDescription):
640
+
641
+ self.object_map[record.level-1].ctreated = record.value #TODO String to timestamp
642
+
643
+ else:
644
+ print(f"Cannot translate 'DATE {type(self.object_map[record.level-1])}")
645
+ assert False
646
+
647
+ def handle_deat(self, record):
648
+ if isinstance(self.object_map[record.level-1], Person):
649
+ gxobject = Fact(type=FactType.Death)
650
+ self.object_map[record.level-1].add_fact(gxobject)
651
+
652
+ self.object_stack.append(gxobject)
653
+ self.object_map[record.level] = gxobject
654
+ else:
655
+ print("Cannot translate 'DEAT")
656
+ assert False
657
+
658
+ def handle_even(self, record: GedcomRecord):
659
+ # TODO If events in a @S, check if only 1 person matches?
660
+ if not record.value.strip() == '':
661
+ values = [value.strip() for value in record.value.split(",")]
662
+ for value in values:
663
+ if value in Translater.gedcom_even_to_fact.keys():
664
+ if isinstance(self.object_map[record.level-1], Person):
665
+ gxobject = Fact(type=Translater.gedcom_even_to_fact[value])
666
+ self.object_map[record.level-1].add_fact(gxobject)
667
+
668
+ self.object_stack.append(gxobject)
669
+ self.object_map[record.level] = gxobject
670
+ print(self.object_map)
671
+ else:
672
+ print(record.describe())
673
+ assert False
674
+ # TODO: Fix, this. making an event to cacth subtags, why are these fact tied to a source? GEDCOM is horrible
675
+ gxobject = Event(type=EventType.UNKNOWN)
676
+ self.object_stack.append(gxobject)
677
+ self.object_map[record.level] = gxobject
678
+ else:
679
+
680
+
681
+ print("This event does not look like a Fact")
682
+
683
+ else:
684
+ possible_fact = FactType.guess(record.subRecord('TYPE')[0].value)
685
+ if possible_fact:
686
+ gxobject = Fact(type=possible_fact)
687
+ self.object_map[record.level-1].add_fact(gxobject)
688
+
689
+ self.object_stack.append(gxobject)
690
+ self.object_map[record.level] = gxobject
691
+ return
692
+ elif EventType.guess(record.subRecord('TYPE')[0].value):
693
+ if isinstance(self.object_map[record.level-1], Person):
694
+ gxobject = Event(type=EventType.guess(record.subRecord('TYPE')[0].value), roles=[EventRole(person=self.object_map[record.level-1], type=EventRoleType.Principal)])
695
+ self.gedcomx.add_event(gxobject)
696
+ self.object_stack.append(gxobject)
697
+ self.object_map[record.level] = gxobject
698
+ return
699
+ else:
700
+ if isinstance(self.object_map[record.level-1], Person):
701
+ gxobject = Event(type=None, roles=[EventRole(person=self.object_map[record.level-1], type=EventRoleType.Principal)])
702
+ gxobject.add_note(Note(subject='Event', text=record.value))
703
+ self.gedcomx.add_event(gxobject)
704
+ self.object_stack.append(gxobject)
705
+ self.object_map[record.level] = gxobject
706
+ return
707
+
708
+ else:
709
+ assert False
710
+
711
+ def handle_fam(self, record: GedcomRecord) -> None:
712
+ if record.tag != 'FAM' or record.level != 0:
713
+ raise ValueError("Invalid record: Must be a level 0 FAM record")
714
+
715
+ husband, wife, children = None, None, []
716
+
717
+ husband_record = record.subRecords('HUSB')
718
+ if husband_record:
719
+ husband = self.gedcomx.person(husband_record[0].value.replace('@',''))
720
+
721
+ wife_record = record.subRecords('WIFE')
722
+ if wife_record:
723
+ wife = self.gedcomx.person(wife_record[0].value.replace('@',''))
724
+
725
+ children_records = record.subRecords('CHIL')
726
+ if children_records:
727
+ for child_record in children_records:
728
+ child = self.gedcomx.person(child_record.value.replace('@',''))
729
+ if child:
730
+ children.append(child)
731
+
732
+ if husband:
733
+ for child in children:
734
+ relationship = Relationship(person1=husband, person2=child, type=RelationshipType.ParentChild)
735
+ self.gedcomx.add_relationship(relationship)
736
+ if wife:
737
+ for child in children:
738
+ relationship = Relationship(person1=wife, person2=child, type=RelationshipType.ParentChild)
739
+ self.gedcomx.add_relationship(relationship)
740
+ if husband and wife:
741
+ relationship = Relationship(person1=husband, person2=wife, type=RelationshipType.Couple)
742
+ self.gedcomx.add_relationship(relationship)
743
+
744
+ def handle_famc(self, record: GedcomRecord) -> None:
745
+ return
746
+
747
+ def handle_fams(self, record: GedcomRecord) -> None:
748
+ return
749
+
750
+ def handle_file(self, record: GedcomRecord):
751
+ if record.value.strip() != '':
752
+ #raise ValueError(f"I did not expect the 'FILE' tag to have a value: {record.value}")
753
+ #TODO Handle files referenced here
754
+ ...
755
+ elif isinstance(self.object_map[record.level-1], SourceDescription):
756
+ ...
757
+ self.object_map[record.level-1].resourceType = ResourceType.DigitalArtifact
758
+
759
+
760
+ def handle_form(self, record: GedcomRecord):
761
+ if record.parent.tag == 'FILE' and isinstance(self.object_map[record.level-2], SourceDescription):
762
+ if record.value.strip() != '':
763
+ mime_type, _ = mimetypes.guess_type('placehold.' + record.value)
764
+ if mime_type:
765
+ self.object_map[record.level-2].mediaType = mime_type
766
+ else:
767
+ print(f"Could not determing mime type from {record.value}")
768
+ else:
769
+ raise ValueError(f"I do not know how to translate the 'FORM' tag with value '{record.value}' for a {type(self.object_map[record.level-1])}")
770
+
771
+ def handle_givn(self, record):
772
+ if isinstance(self.object_map[record.level-1], Name):
773
+ given_name = NamePart(value=record.value, type=NamePartType.Given)
774
+ self.object_map[record.level-1]._add_name_part(given_name)
775
+ else:
776
+ print(f"{record.describe()}, Cannot translate 'NAME', {self.object_map[record.level-1]}")
777
+ assert False
778
+
779
+ def handle_indi(self, record):
780
+ person = Person(id=record.xref.replace('@',''))
781
+ self.gedcomx.add_person(person)
782
+ self.object_stack.append(person)
783
+ self.object_map[record.level] = person
784
+
785
+ def handle_immi(self, record):
786
+ if isinstance(self.object_map[record.level-1], Person):
787
+ gxobject = Fact(type=FactType.Immigration)
788
+ self.object_map[record.level-1].add_fact(gxobject)
789
+
790
+ self.object_stack.append(gxobject)
791
+ self.object_map[record.level] = gxobject
792
+ else:
793
+ print("Cannot translate 'IMMI")
794
+ assert False
795
+
796
+ def handle_marr(self, record):
797
+ if isinstance(self.object_map[record.level-1], Person):
798
+ gxobject = Fact(type=FactType.Marriage)
799
+ self.object_map[record.level-1].add_fact(gxobject)
800
+
801
+ self.object_stack.append(gxobject)
802
+ self.object_map[record.level] = gxobject
803
+ else:
804
+ print("Cannot translate 'MARR")
805
+ assert False
806
+
807
+ def handle_name(self, record):
808
+ if isinstance(self.object_map[record.level-1], Person):
809
+ gxobject = Name.simple(record.value)
810
+ #gxobject = Name(nameForms=[NameForm(fullText=record.value)], type=NameType.BirthName)
811
+ self.object_map[record.level-1].add_name(gxobject)
812
+
813
+ self.object_stack.append(gxobject)
814
+ self.object_map[record.level] = gxobject
815
+ elif isinstance(self.object_map[record.level-1], Agent):
816
+ gxobject = TextValue(value=record.value)
817
+ self.object_map[record.level-1].add_name(gxobject)
818
+ else:
819
+ print(f"{record.describe()}, Cannot translate 'NAME', {self.object_map[record.level-1]}")
820
+ assert False
821
+
822
+ def handle_note(self, record):
823
+ if isinstance(self.object_map[record.level-1], SourceDescription):
824
+ gxobject = Note(text=Translater.clean_str(record.value))
825
+ self.object_map[record.level-1].add_note(gxobject)
826
+
827
+ self.object_stack.append(gxobject)
828
+ self.object_map[record.level] = gxobject
829
+ elif isinstance(self.object_map[record.level-1], SourceReference):
830
+ gxobject = Note(text=Translater.clean_str(record.value))
831
+ self.object_map[record.level-1]._description_object.add_note(gxobject)
832
+
833
+ self.object_stack.append(gxobject)
834
+ self.object_map[record.level] = gxobject
835
+ elif isinstance(self.object_map[record.level-1], Conclusion):
836
+ gxobject = Note(text=record.value)
837
+ self.object_map[record.level-1].add_note(gxobject)
838
+
839
+ self.object_stack.append(gxobject)
840
+ self.object_map[record.level] = gxobject
841
+ else:
842
+ raise ValueError(f"Could not handle 'NOTE' tag in record {record.describe()}, last stack object {type(self.object_map[record.level-1])}")
843
+ assert False
844
+
845
+ def handle_nsfx(self, record):
846
+ if isinstance(self.object_map[record.level-1], Name):
847
+ surname = NamePart(value=record.value, type=NamePartType.Suffix)
848
+ self.object_map[record.level-1]._add_name_part(surname)
849
+ else:
850
+ print(f"{record.describe()}, Cannot translate 'NSFX', {self.object_map[record.level-1]}")
851
+ assert False
852
+
853
+ def handle_occu(self, record):
854
+ if isinstance(self.object_map[record.level-1], Person):
855
+ gxobject = Fact(type=FactType.Occupation)
856
+ self.object_map[record.level-1].add_fact(gxobject)
857
+
858
+ self.object_stack.append(gxobject)
859
+ self.object_map[record.level] = gxobject
860
+ else:
861
+ print("Cannot translate 'OCCU")
862
+ assert False
863
+
864
+ def handle_obje(self, record: GedcomRecord):
865
+ self.handle_sour(record)
866
+
867
+ def handle_page(self, record):
868
+ if isinstance(self.object_map[record.level-1], SourceReference):
869
+ self.object_map[record.level-1].descriptionId = record.value
870
+ self.object_map[record.level-1].add_qualifier(KnownSourceReference.Page)
871
+
872
+ #self.object_stack.append(gxobject)
873
+ #self.object_map[record.level] = gxobject
874
+ self.object_map[record.level] = self.object_map[record.level-1]
875
+ else:
876
+ raise ValueError(f"Could not handle 'PAGE' tag in record {record.describe()}, last stack object {self.object_map[record.level-1]}")
877
+
878
+ def handle_plac(self, record):
879
+ if isinstance(self.object_map[record.level-1], Agent):
880
+ gxobject = Address(value=record.value)
881
+ self.object_map[record.level-1].add_address(gxobject)
882
+
883
+ self.object_stack.append(gxobject)
884
+ self.object_map[record.level] = gxobject
885
+ elif isinstance(self.object_map[record.level-1], Event):
886
+ if self.gedcomx.places.byName(record.value):
887
+ self.object_map[record.level-1].place = PlaceReference(original=record.value, descriptionRef=self.gedcomx.places.byName(record.value)[0])
888
+ else:
889
+ place_des = PlaceDescription(names=[TextValue(value=record.value)])
890
+ self.gedcomx.add_place_description(place_des)
891
+ self.object_map[record.level-1].place = PlaceReference(original=record.value, descriptionRef=place_des)
892
+ elif isinstance(self.object_map[record.level-1], Fact):
893
+ if self.gedcomx.places.byName(record.value):
894
+ self.object_map[record.level-1].place = PlaceReference(original=record.value, descriptionRef=self.gedcomx.places.byName(record.value)[0])
895
+ else:
896
+ place_des = PlaceDescription(names=[TextValue(value=record.value)])
897
+ self.gedcomx.add_place_description(place_des)
898
+ self.object_map[record.level-1].place = PlaceReference(original=record.value, descriptionRef=place_des)
899
+ elif isinstance(self.object_map[record.level-1], SourceDescription):
900
+ gxobject = Note(text='Place: ' + record.value)
901
+ self.object_map[record.level-1].add_note(gxobject)
902
+ self.object_stack.append(gxobject)
903
+ self.object_map[record.level] = gxobject
904
+ else:
905
+ print("Cannot translate 'PLACE")
906
+ assert False
907
+
908
+ def handle_post(self, record):
909
+ if isinstance(self.object_map[record.level-1], Address):
910
+ self.object_map[record.level-1].postalCode = Translater.clean_str(record.value)
911
+ else:
912
+ raise ValueError(f"I do not know how to handle an 'POST' tag for a {type(self.object_map[record.level-1])}")
913
+
914
+ def handle_publ(self, record):
915
+ if isinstance(self.object_map[record.level-1], SourceDescription):
916
+ if self.gedcomx.agents.byName(record.value):
917
+ gxobject = self.gedcomx.agents.byName(record.value)[0]
918
+ else:
919
+ gxobject = Agent(names=[TextValue(record.value)])
920
+ self.gedcomx.add_agent(gxobject)
921
+ self.object_map[record.level-1].publisher = gxobject
922
+
923
+ self.object_stack.append(gxobject)
924
+ self.object_map[record.level] = gxobject
925
+ else:
926
+ print("Cannot translate 'PUBL")
927
+ assert False
928
+
929
+ def handle_prob(self, record):
930
+ if isinstance(self.object_map[record.level-1], Person):
931
+ gxobject = Fact(type=FactType.Probate)
932
+ self.object_map[record.level-1].add_fact(gxobject)
933
+
934
+ self.object_stack.append(gxobject)
935
+ self.object_map[record.level] = gxobject
936
+ else:
937
+ print("Cannot translate 'PROB")
938
+ assert False
939
+
940
+ def handle_refn(self, record):
941
+ if isinstance(self.object_map[record.level-1], Person) or isinstance(self.object_map[record.level-1], SourceDescription):
942
+ self.object_map[record.level-1].add_identifier(Identifier(value=URI.from_url('Reference Number:' + record.value)))
943
+ else:
944
+ raise ValueError(f"Could not handle 'REFN' tag in record {record.describe()}, last stack object {type(self.object_map[record.level-1])}")
945
+
946
+ def handle_repo(self, record):
947
+ if record.level == 0:
948
+
949
+ gxobject = Agent(id=record.value.replace('@',''))
950
+ self.gedcomx.add_agent(gxobject)
951
+ self.object_stack.append(gxobject)
952
+ self.object_map[record.level] = gxobject
953
+
954
+ elif isinstance(self.object_map[record.level-1], SourceDescription):
955
+ if self.gedcomx.agents.byId(record.value.replace('@','')):
956
+ # TODO WHere and what to add this to?
957
+ gxobject = self.gedcomx.agents.byId(record.value)
958
+ self.object_map[record.level-1].repository = gxobject
959
+ else:
960
+ print(record.describe())
961
+ raise ValueError()
962
+ gxobject = Agent(names=[TextValue(record.value)])
963
+ else:
964
+ raise ValueError(f"I do not know how to handle 'REPO' tag that is not a top-level, or sub-tag of {type(self.object_map[record.level-1])}")
965
+
966
+
967
+ self.object_stack.append(gxobject)
968
+ self.object_map[record.level] = gxobject
969
+
970
+ def handle_resi(self, record):
971
+ if isinstance(self.object_map[record.level-1], Person):
972
+ gxobject = Fact(type=FactType.Residence)
973
+ if record.value.strip() != '':
974
+ gxobject.add_note(Note(text=record.value))
975
+ self.object_map[record.level-1].add_fact(gxobject)
976
+
977
+ self.object_stack.append(gxobject)
978
+ self.object_map[record.level] = gxobject
979
+ else:
980
+ print("Cannot translate 'RESI")
981
+ assert False
982
+
983
+ def handle_rin(self, record):
984
+ if isinstance(self.object_map[record.level-1], SourceDescription):
985
+ self.object_map[record.level-1].id = record.value
986
+ self.object_map[record.level-1].add_note(Note(text=f"Source had RIN: of {record.value}"))
987
+
988
+ else:
989
+ raise ValueError(f"Could not handle 'RIN' tag in record {record.describe()}, last stack object {type(self.object_map[record.level-1])}")
990
+
991
+ def handle_sex(self, record: GedcomRecord):
992
+
993
+ if isinstance(self.object_map[record.level-1], Person):
994
+ if record.value == 'M':
995
+ gxobject = Gender(type=GenderType.Male)
996
+ elif record.value == 'F':
997
+ gxobject = Gender(type=GenderType.Female)
998
+ else:
999
+ gxobject = Gender(type=GenderType.Unknown)
1000
+ self.object_map[record.level-1].gender = gxobject
1001
+
1002
+ self.object_stack.append(gxobject)
1003
+ self.object_map[record.level] = gxobject
1004
+ else:
1005
+ assert False
1006
+
1007
+ def handle_sour(self, record):
1008
+ if record.level == 0 or record.tag == '_WLNK' or (record.level == 0 and record.tag == 'OBJE'):
1009
+ source_description = SourceDescription(id=record.xref)
1010
+ self.gedcomx.add_source_description(source_description)
1011
+ self.object_stack.append(source_description)
1012
+ self.object_map[record.level] = source_description
1013
+ else:
1014
+ if self.gedcomx.source_descriptions.byId(record.xref):
1015
+ gxobject = SourceReference(descriptionId=record.xref, description=self.gedcomx.source_descriptions.byId(record.xref))
1016
+ else:
1017
+ source_description = SourceDescription(id=record.xref)
1018
+ gxobject = SourceReference(descriptionId=record.value, description=source_description)
1019
+ if isinstance(self.object_map[record.level-1],SourceReference):
1020
+ self.object_map[record.level-1]._description_object.add_source(gxobject)
1021
+ else:
1022
+ self.object_map[record.level-1].add_source(gxobject)
1023
+ self.object_stack.append(gxobject)
1024
+ self.object_map[record.level] = gxobject
1025
+
1026
+
1027
+ def handle_stae(self, record):
1028
+ if isinstance(self.object_map[record.level-1], Address):
1029
+ self.object_map[record.level-1].stateOrProvince = Translater.clean_str(record.value)
1030
+ else:
1031
+ raise ValueError(f"I do not know how to handle an 'STAE' tag for a {type(self.object_map[record.level-1])}")
1032
+
1033
+ def handle_surn(self, record):
1034
+ if isinstance(self.object_map[record.level-1], Name):
1035
+ surname = NamePart(value=record.value, type=NamePartType.Surname)
1036
+ self.object_map[record.level-1]._add_name_part(surname)
1037
+ else:
1038
+ print(f"{record.describe()}, Cannot translate 'NAME', {self.object_map[record.level-1]}")
1039
+ assert False
1040
+
1041
+ def handle_text(self, record: GedcomRecord):
1042
+ if record.parent.tag == 'DATA':
1043
+ if isinstance(self.object_map[record.level-2], SourceReference):
1044
+ gxobject = TextValue(value=record.value)
1045
+ self.object_map[record.level-2]._description_object.add_description(gxobject)
1046
+ self.object_stack.append(gxobject)
1047
+ self.object_map[record.level] = gxobject
1048
+ else:
1049
+ assert False
1050
+
1051
+ def handle_titl(self, record):
1052
+ if isinstance(self.object_map[record.level-1], SourceDescription):
1053
+
1054
+ gxobject = TextValue(value=Translater.clean_str(record.value))
1055
+ self.object_map[record.level-1].add_title(gxobject)
1056
+
1057
+ self.object_stack.append(gxobject)
1058
+ self.object_map[record.level] = gxobject
1059
+
1060
+ elif record.parent.tag == 'FILE' and isinstance(self.object_map[record.level-2], SourceDescription):
1061
+ gxobject = TextValue(value=record.value)
1062
+ self.object_map[record.level-2].add_title(gxobject)
1063
+
1064
+ self.object_stack.append(gxobject)
1065
+ self.object_map[record.level] = gxobject
1066
+ elif self.object_map[record.level] and isinstance(self.object_map[record.level], Name):
1067
+ gxobject = NamePart(value=record.value, qualifiers=[NamePartQualifier.Title])
1068
+
1069
+ self.object_map[record.level]._add_name_part(gxobject)
1070
+ else:
1071
+ print(self.object_map)
1072
+ print(self.object_map[record.level])
1073
+ raise ValueError(f"Could not handle 'TITL' tag in record {record.describe()}, last stack object {type(self.object_map[record.level-1])}")
1074
+ assert False
1075
+
1076
+ def handle_type(self, record):
1077
+ # peek to see if event or fact
1078
+ if isinstance(self.object_map[record.level-1], Event):
1079
+ if EventType.guess(record.value):
1080
+ self.object_map[record.level-1].type = EventType.guess(record.value)
1081
+ else:
1082
+ self.object_map[record.level-1].type = None
1083
+ self.object_map[record.level-1].add_note(Note(text=Translater.clean_str(record.value)))
1084
+ elif isinstance(self.object_map[record.level-1], Fact):
1085
+ if not self.object_map[record.level-1].type:
1086
+ self.object_map[0].type = FactType.guess(record.value)
1087
+ elif record.parent.tag == 'FORM':
1088
+ if not self.object_map[0].mediaType:
1089
+ self.object_map[0].mediaType = record.value
1090
+
1091
+ else:
1092
+ raise ValueError(f"I do not know how to handle 'TYPE' tag for {type(self.object_map[record.level-1])}")
1093
+
1094
+ def handle__url(self, record):
1095
+ if isinstance(self.object_map[record.level-2], SourceDescription):
1096
+ self.object_map[record.level-2].about = URI.from_url(record.value)
1097
+ else:
1098
+ raise ValueError(f"Could not handle '_URL' tag in record {record.describe()}, last stack object {self.object_map[record.level-1]}")
1099
+
1100
+ def handle_www(self, record):
1101
+ if isinstance(self.object_map[record.level-2], SourceReference):
1102
+ self.object_map[record.level-2]._description_object.add_identifier(Identifier(value=URI.from_url(record.value)))
1103
+ else:
1104
+ raise ValueError(f"Could not handle 'WWW' tag in record {record.describe()}, last stack object {self.object_map[record.level-1]}")
1105
+