gedcom-x 0.5__py3-none-any.whl → 0.5.2__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/Date.py CHANGED
@@ -1,29 +1,70 @@
1
1
  from typing import Optional
2
+ from datetime import datetime, timezone
3
+ from dateutil import parser
4
+ import time
2
5
 
3
6
 
4
7
  class DateFormat:
5
8
  def __init__(self) -> None:
6
9
  pass
7
-
10
+
11
+ class DateNormalization():
12
+ pass
8
13
 
9
14
  class Date:
10
15
  identifier = 'http://gedcomx.org/v1/Date'
11
16
  version = 'http://gedcomx.org/conceptual-model/v1'
12
17
 
13
- def __init__(self, original: Optional[str],formal: Optional[str | DateFormat] = None) -> None:
18
+ def __init__(self, original: Optional[str],normalized: Optional[DateNormalization] = None ,formal: Optional[str | DateFormat] = None) -> None:
14
19
  self.orginal = original
15
20
  self.formal = formal
21
+
22
+ self.normalized: DateNormalization | None = normalized if normalized else None
16
23
 
17
24
  def _prop_dict(self):
18
25
  return {'original': self.orginal,
19
26
  'formal': self.formal}
20
27
 
21
- # Date
22
- Date._from_json_ = classmethod(lambda cls, data: Date(
23
- original=data.get('original'),
24
- formal=data.get('formal')
25
- ))
28
+ @classmethod
29
+ def _from_json_(obj,data):
30
+ original = data.get('original',None)
31
+ formal = data.get('formal',None)
32
+
33
+ return Date(original=original,formal=formal)
34
+
26
35
 
27
36
  Date._to_dict_ = lambda self: {
28
37
  'original': self.orginal,
29
- 'formal': self.formal}
38
+ 'formal': self.formal}
39
+
40
+
41
+
42
+
43
+ def date_to_timestamp(date_str: str, assume_utc_if_naive: bool = True, print_definition: bool = True):
44
+ """
45
+ Convert a date string of various formats into a Unix timestamp.
46
+
47
+ A "timestamp" refers to an instance of time, including values for year,
48
+ month, date, hour, minute, second, and timezone.
49
+ """
50
+ # Handle year ranges like "1894-1912" → pick first year
51
+ if "-" in date_str and date_str.count("-") == 1 and all(part.isdigit() for part in date_str.split("-")):
52
+ date_str = date_str.split("-")[0].strip()
53
+
54
+ # Parse date
55
+ dt = parser.parse(date_str)
56
+
57
+ # Ensure timezone awareness
58
+ if dt.tzinfo is None:
59
+ dt = dt.replace(tzinfo=timezone.utc if assume_utc_if_naive else datetime.now().astimezone().tzinfo)
60
+
61
+ # Normalize to UTC and compute timestamp
62
+ dt_utc = dt.astimezone(timezone.utc)
63
+ epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
64
+ ts = (dt_utc - epoch).total_seconds()
65
+
66
+ # Create ISO 8601 string with full date/time/timezone
67
+ full_timestamp_str = dt_utc.replace(microsecond=0).isoformat()
68
+
69
+
70
+ return ts, full_timestamp_str
gedcomx/Document.py CHANGED
@@ -1,11 +1,11 @@
1
1
  from enum import Enum
2
- from typing import Optional
2
+ from typing import Optional, List
3
3
 
4
4
  from gedcomx.Attribution import Attribution
5
- from gedcomx.Conclusion import ConfidenceLevel
5
+ #from gedcomx.Conclusion import ConfidenceLevel
6
6
  from gedcomx.Note import Note
7
7
  from gedcomx.SourceReference import SourceReference
8
- from gedcomx.URI import URI
8
+ from gedcomx.Resource import Resource
9
9
 
10
10
  from .Conclusion import Conclusion
11
11
 
@@ -33,10 +33,20 @@ class Document(Conclusion):
33
33
  identifier = 'http://gedcomx.org/v1/Document'
34
34
  version = 'http://gedcomx.org/conceptual-model/v1'
35
35
 
36
- def __init__(self, id: str | None, lang: str | None, sources: SourceReference | None, analysis: URI | None, notes: Note | None, confidence: ConfidenceLevel | None, attribution: Attribution | None,
37
- type: Optional[DocumentType],
38
- extracted: Optional[bool], # Default to False
39
- textType: Optional[TextType],
40
- text: str,
36
+ def __init__(self, id: Optional[str] = None,
37
+ lang: Optional[str] = None,
38
+ sources: Optional[List[SourceReference]] = None,
39
+ analysis: Optional[Resource] = None,
40
+ notes: Optional[List[Note]] = None,
41
+ confidence: Optional[object] = None, # ConfidenceLevel
42
+ attribution: Optional[Attribution] = None,
43
+ type: Optional[DocumentType] = None,
44
+ extracted: Optional[bool] = None, # Default to False
45
+ textType: Optional[TextType] = None,
46
+ text: Optional[str] = None,
41
47
  ) -> None:
42
- super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
48
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
49
+ self.type = type
50
+ self.extracted = extracted
51
+ self.textType = textType
52
+ self.text = text
gedcomx/Event.py CHANGED
@@ -11,7 +11,7 @@ from .Note import Note
11
11
  from .PlaceReference import PlaceReference
12
12
  from .SourceReference import SourceReference
13
13
  from .Subject import Subject
14
- from .URI import URI
14
+ from .Resource import Resource
15
15
 
16
16
  class EventRoleType(Enum):
17
17
  Principal = "http://gedcomx.org/Principal"
@@ -37,11 +37,11 @@ class EventRole(Conclusion):
37
37
  id: Optional[str] = None,
38
38
  lang: Optional[str] = 'en',
39
39
  sources: Optional[List[SourceReference]] = [],
40
- analysis: Optional[URI] = None,
40
+ analysis: Optional[Resource] = None,
41
41
  notes: Optional[List[Note]] = [],
42
42
  confidence: Optional[ConfidenceLevel] = None,
43
43
  attribution: Optional[Attribution] = None,
44
- person: URI = None,
44
+ person: Resource = None,
45
45
  type: Optional[EventRoleType] = None,
46
46
  details: Optional[str] = None) -> None:
47
47
  super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
@@ -180,7 +180,7 @@ class Event(Subject):
180
180
  id: Optional[str] = None,
181
181
  lang: Optional[str] = 'en',
182
182
  sources: Optional[List[SourceReference]] = [],
183
- analysis: Optional[URI] = None,
183
+ analysis: Optional[Resource] = None,
184
184
  notes: Optional[List[Note]] = [],
185
185
  confidence: Optional[ConfidenceLevel] = None,
186
186
  attribution: Optional[Attribution] = None,
@@ -1,11 +1,11 @@
1
1
  from typing import Optional
2
2
 
3
3
  from .Attribution import Attribution
4
- from .URI import URI
4
+ from .Resource import Resource
5
5
 
6
6
  class EvidenceReference:
7
7
  identifier = 'http://gedcomx.org/v1/EvidenceReference'
8
8
  version = 'http://gedcomx.org/conceptual-model/v1'
9
9
 
10
- def __init__(self, resource: URI, attribution: Optional[Attribution]) -> None:
10
+ def __init__(self, resource: Resource, attribution: Optional[Attribution]) -> None:
11
11
  pass
gedcomx/Exceptions.py ADDED
@@ -0,0 +1,10 @@
1
+
2
+
3
+ class GedcomXError(Exception):
4
+ """Base for all app-specific errors."""
5
+
6
+ class TagConversionError(GedcomXError):
7
+ def __init__(self, record,levelstack):
8
+ 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__}"
9
+ super().__init__(msg)
10
+
gedcomx/Fact.py CHANGED
@@ -3,21 +3,27 @@ import re
3
3
 
4
4
  from datetime import datetime
5
5
  from enum import Enum
6
- from typing import List, Optional
6
+ from typing import List, Optional, Dict, Any
7
7
 
8
8
  from .Attribution import Attribution
9
9
  from .Conclusion import ConfidenceLevel
10
+ from .Document import Document
10
11
  from .Date import Date
11
12
  from .Note import Note
12
13
  from .PlaceReference import PlaceReference
13
14
  from .SourceReference import SourceReference
14
- from .URI import URI
15
+ from .Serialization import Serialization
16
+ from .Resource import Resource
15
17
 
16
18
  from .Conclusion import Conclusion
17
19
  from .Qualifier import Qualifier
18
20
 
19
21
  from enum import Enum
20
22
 
23
+ from collections.abc import Sized
24
+
25
+ from ._Links import _Link, _LinkList
26
+
21
27
 
22
28
  class FactType(Enum):
23
29
  # Person Fact Types
@@ -390,10 +396,10 @@ class Fact(Conclusion):
390
396
  version = 'http://gedcomx.org/conceptual-model/v1'
391
397
 
392
398
  def __init__(self,
393
- id: str = None,
399
+ id: Optional[str] = None,
394
400
  lang: str = 'en',
395
401
  sources: Optional[List[SourceReference]] = [],
396
- analysis: URI = None,
402
+ analysis: Optional[Resource | Document] = None,
397
403
  notes: Optional[List[Note]] = [],
398
404
  confidence: ConfidenceLevel = None,
399
405
  attribution: Attribution = None,
@@ -401,62 +407,80 @@ class Fact(Conclusion):
401
407
  date: Optional[Date] = None,
402
408
  place: Optional[PlaceReference] = None,
403
409
  value: Optional[str] = None,
404
- qualifiers = []) -> None: #qualifiers: Optional[List[FactQualifier]] = []) -> None:
405
- super().__init__(id, lang, sources, analysis, notes, confidence, attribution)
410
+ qualifiers: Optional[List[FactQualifier]] = None,
411
+ links: Optional[_LinkList] = None):
412
+ super().__init__(id, lang, sources, analysis, notes, confidence, attribution, links=links)
406
413
  self.type = type
407
414
  self.date = date
408
415
  self.place = place
409
416
  self.value = value
410
- self.qualifiers = qualifiers
417
+ self._qualifiers = qualifiers if qualifiers else []
411
418
 
419
+
412
420
  @property
413
- def _as_dict_(self):
414
- def _serialize(value):
415
- if isinstance(value, (str, int, float, bool, type(None))):
416
- return value
417
- elif isinstance(value, dict):
418
- return {k: _serialize(v) for k, v in value.items()}
419
- elif isinstance(value, (list, tuple, set)):
420
- return [_serialize(v) for v in value]
421
- elif hasattr(value, "_as_dict_"):
422
- return value._as_dict_
423
- else:
424
- return str("UKN " + value) # fallback for unknown objects
421
+ def qualifiers(self) -> List[FactQualifier]:
422
+ return self._qualifiers # type: ignore
425
423
 
424
+ @qualifiers.setter
425
+ def qualifiers(self, value: List[FactQualifier]):
426
+ if (not isinstance(value, list)) or (not all(isinstance(item, FactQualifier) for item in value)):
427
+ raise ValueError("sources must be a list of GedcomRecord objects.")
428
+ self._qualifiers.extend(value)
429
+
430
+ @property
431
+ def _as_dict_(self):
432
+ fact_dict = super()._as_dict_
426
433
  # Only add Relationship-specific fields
427
- fact_fields = {
434
+ fact_dict.update( {
428
435
  'type': self.type.value if self.type else None,
429
436
  'date': self.date._prop_dict() if self.date else None,
430
437
  'place': self.place._as_dict_ if self.place else None,
431
438
  'value': self.value,
432
439
  'qualifiers': [q.value for q in self.qualifiers] if self.qualifiers else []
433
- }
434
-
435
- # Serialize and exclude None values
436
- for key, value in fact_fields.items():
437
- if value is not None:
438
- fact_fields[key] = _serialize(value)
440
+ })
439
441
 
440
442
 
441
- return fact_fields
443
+ return Serialization.serialize_dict(fact_dict)
444
+
445
+ @classmethod
446
+ def _from_json_(cls, data: Dict[str, Any]) -> 'Fact':
447
+
448
+ # Extract fields, no trailing commas!
449
+ id_ = data.get('id')
450
+ lang = data.get('lang', 'en')
451
+ sources = [SourceReference._from_json_(s) for s in data.get('sources',[])]
452
+ analysis = (Resource._from_json_(data['analysis'])
453
+ if data.get('analysis') else None)
454
+ notes = [Note._from_json_(n) for n in data.get('notes',[])]
455
+ confidence = (ConfidenceLevel._from_json_(data['confidence'])
456
+ if data.get('confidence') else None)
457
+ attribution = (Attribution._from_json_(data['attribution']) if data.get('attribution') else None)
458
+ fact_type = (FactType.from_value(data['type'])
459
+ if data.get('type') else None)
460
+ date = (Date._from_json_(data['date'])
461
+ if data.get('date') else None)
462
+ place = (PlaceReference._from_json_(data['place'])
463
+ if data.get('place') else None)
464
+ value = data.get('value')
465
+ 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
467
+
468
+ return cls(
469
+ id=id_,
470
+ lang=lang,
471
+ sources=sources,
472
+ analysis=analysis,
473
+ notes=notes,
474
+ confidence=confidence,
475
+ attribution=attribution,
476
+ type=fact_type,
477
+ date=date,
478
+ place=place,
479
+ value=value,
480
+ qualifiers=qualifiers,
481
+ links=links
482
+ )
483
+
484
+
442
485
 
443
- def ensure_list(val):
444
- if val is None:
445
- return []
446
- return val if isinstance(val, list) else [val]
447
- # Fact
448
- Fact._from_json_ = classmethod(lambda cls, data: cls(
449
- id=data.get('id'),
450
- lang=data.get('lang', 'en'),
451
- sources=[SourceReference._from_json_(s) for s in ensure_list(data.get('sources'))],
452
- analysis=URI._from_json_(data['analysis']) if data.get('analysis') else None,
453
- notes=[Note._from_json_(n) for n in ensure_list(data.get('notes'))],
454
- confidence=ConfidenceLevel._from_json_(data['confidence']) if data.get('confidence') else None,
455
- attribution=Attribution._from_json_(data['attribution']) if data.get('attribution') else None,
456
- type=FactType.from_value(data['type']) if data.get('type') else None,
457
- date=Date._from_json_(data['date']) if data.get('date') else None,
458
- place=PlaceReference._from_json_(data['place']) if data.get('place') else None,
459
- value=data.get('value'),
460
- qualifiers=[Qualifier._from_json_(q) for q in ensure_list(data.get('qualifiers'))]
461
- ))
462
486
 
gedcomx/Gedcom.py CHANGED
@@ -3,10 +3,23 @@
3
3
 
4
4
  import html
5
5
  import os
6
- from typing import List, Optional
6
+ from typing import List, Optional, Tuple
7
+ import re
7
8
 
8
9
  BOM = '\ufeff'
9
10
 
11
+ GEDCOM7_LINE_RE = re.compile(
12
+ r"""^
13
+ (?P<level>\d+) # Level
14
+ (?:\s+@(?P<xref>[^@]+)@)? # Optional record identifier
15
+ \s+(?P<tag>[A-Z0-9_-]+) # Tag
16
+ (?:\s+(?P<value>.+))? # Optional value (may be XREF)
17
+ $""",
18
+ re.VERBOSE
19
+ )
20
+
21
+ XREF_RE = re.compile(r'^@[^@]+@$')
22
+
10
23
  # Add hash table for XREF of Zero Recrods?
11
24
 
12
25
  nonzero = '[1-9]'
@@ -30,8 +43,9 @@ eol = '(\\\r(\\\n)?|\\\n)'
30
43
  line = f'{level}{d}((?P<xref>{xref}){d})?(?P<tag>{tag})({d}{lineval})?{eol}'
31
44
 
32
45
  class GedcomRecord():
33
- def __init__(self,line_num=None,level=-1, tag='NONR', xref='', value=None) -> None:
34
- self.line_num = line_num
46
+
47
+ def __init__(self,line_num: Optional[int] =None,level: int =-1, tag='NONR', xref: Optional[str] = None, value: Optional[str] = None) -> None:
48
+ self.line = line_num
35
49
  self._subRecords = []
36
50
  self.level = int(level)
37
51
  self.xref = xref
@@ -42,10 +56,10 @@ class GedcomRecord():
42
56
  self.parent = None
43
57
  self.root = None
44
58
 
45
- if self.value.endswith('@') and self.value.startswith('@'):
46
- self.xref = self.value.replace('@','')
47
- if level > 0:
48
- self.pointer = True
59
+ #if self.value and (self.value.endswith('@') and self.value.startswith('@')):
60
+ # self.xref = self.value.replace('@','')
61
+ # if level > 0:
62
+ # self.pointer = True
49
63
 
50
64
  @property
51
65
  def _as_dict_(self):
@@ -67,7 +81,7 @@ class GedcomRecord():
67
81
  raise ValueError(f"SubRecord must be next level from this record (level:{self.level}, subRecord has level {record.level})")
68
82
 
69
83
  def recordOnly(self):
70
- return GedcomRecord(line_num=self.line_num,level=self.level,tag=self.tag,value=self.value)
84
+ return GedcomRecord(line_num=self.line,level=self.level,tag=self.tag,value=self.value)
71
85
 
72
86
  def dump(self):
73
87
  record_dump = f"Level: {self.level}, tag: {self.tag}, value: {self.value}, subRecords: {len(self._subRecords)}\n"
@@ -76,7 +90,8 @@ class GedcomRecord():
76
90
  return record_dump
77
91
 
78
92
  def describe(self,subRecords: bool = False):
79
- description = f"Line {self.line_num}: {'\t'* self.level} Level: {self.level}, tag: '{self.tag}', value: '{self.value}', subRecords: {len(self._subRecords)}"
93
+ level_str = '\t'* self.level
94
+ description = f"Line {self.line}: {level_str} Level: {self.level}, tag: '{self.tag}', xref={self.xref} value: '{self.value}', subRecords: {len(self._subRecords)}"
80
95
  if subRecords:
81
96
  for subRecord in self.subRecords():
82
97
  description = description + '\n' + subRecord.describe(subRecords=True)
@@ -127,13 +142,25 @@ class GedcomRecord():
127
142
  yield from self._flatten_subrecords(subrecord)
128
143
 
129
144
  class Gedcom():
130
- top_level_tags = ['INDI', 'FAM', 'OBJE', 'SOUR', 'REPO', 'NOTE', 'HEAD']
131
-
132
- # =========================================================
133
- # 1. INITIALIZATION
134
- # =========================================================
145
+ """
146
+ Object representing a Genealogy in legacy GEDCOM 5.x / 7 format.
147
+
148
+ Parameters
149
+ ----------
150
+ records : List[GedcomReord]
151
+ List of GedcomRecords to initialize the genealogy with
152
+ filepath : str
153
+ path to a GEDCOM (``*``.ged), if provided object will read, parse and initialize with records in the file.
135
154
 
136
-
155
+ Note
156
+ ----
157
+ **file_path** takes precidence over **records**.
158
+ If no arguments are provided, Gedcom Object will initialize with no records.
159
+
160
+
161
+ """
162
+ _top_level_tags = ['INDI', 'FAM', 'OBJE', 'SOUR', 'REPO', 'NOTE', 'HEAD','SNOTE']
163
+
137
164
  def __init__(self, records: Optional[List[GedcomRecord]] = None,filepath: str = None) -> None:
138
165
  if filepath:
139
166
  self.records = self._records_from_file(filepath)
@@ -141,34 +168,42 @@ class Gedcom():
141
168
  self.records: List[GedcomRecord] = records if records else []
142
169
 
143
170
 
171
+
144
172
  self._sources = []
145
173
  self._repositories = []
146
174
  self._individuals = []
147
175
  self._families = []
148
176
  self._objects = []
177
+ self._snotes = []
149
178
 
150
179
  if self.records:
151
180
  for record in self.records:
152
181
  if record.tag == 'INDI':
153
- record.xref = record.value
182
+
154
183
  self._individuals.append(record)
155
184
  if record.tag == 'SOUR' and record.level == 0:
156
- record.xref = record.value
185
+
157
186
  self._sources.append(record)
158
187
  if record.tag == 'REPO' and record.level == 0:
159
- record.xref = record.value
188
+ print(record.describe())
189
+
160
190
  self._repositories.append(record)
161
191
  if record.tag == 'FAM' and record.level == 0:
162
- record.xref = record.value
192
+
163
193
  self._families.append(record)
164
194
  if record.tag == 'OBJE' and record.level == 0:
165
- record.xref = record.value
195
+
166
196
  self._objects.append(record)
197
+ if record.tag == 'SNOTE' and record.level == 0:
198
+
199
+ record.xref = record.value
200
+ self._snotes.append(record)
167
201
 
202
+
168
203
  # =========================================================
169
204
  # 2. PROPERTY ACCESSORS (GETTERS & SETTERS)
170
205
  # =========================================================
171
-
206
+
172
207
  @property
173
208
  def json(self):
174
209
  import json
@@ -215,6 +250,9 @@ class Gedcom():
215
250
 
216
251
  @property
217
252
  def repositories(self) -> List[GedcomRecord]:
253
+ """
254
+ List of **REPO** records found in the Genealogy
255
+ """
218
256
  return self._repositories
219
257
 
220
258
  @repositories.setter
@@ -253,18 +291,40 @@ class Gedcom():
253
291
  raise ValueError("objects must be a list of GedcomRecord objects.")
254
292
  self._objects = value
255
293
 
256
- # =========================================================
257
- # 3. METHODS
258
- # =========================================================
294
+
259
295
 
260
- def write(self):
296
+ def write(self) -> bool:
261
297
  """
262
298
  Method placeholder for writing GEDCOM files.
299
+
300
+ Raises
301
+ ------
302
+ NotImplementedError
303
+ writing to legacy GEDCOM file is not currently implimented.
263
304
  """
264
305
  raise NotImplementedError("Writing of GEDCOM files is not implemented.")
265
306
 
266
307
  @staticmethod
267
308
  def _records_from_file(filepath: str) -> List[GedcomRecord]:
309
+ def parse_gedcom7_line(line: str) -> Optional[Tuple[int, Optional[str], str, Optional[str], Optional[str]]]:
310
+ """
311
+ Parse a GEDCOM 7 line into: level, xref_id (record), tag, value, xref_value (if value is an @X@)
312
+
313
+ Returns:
314
+ (level, xref_id, tag, value, xref_value)
315
+ """
316
+ match = GEDCOM7_LINE_RE.match(line.strip())
317
+ if not match:
318
+ return None
319
+
320
+ level = int(match.group("level"))
321
+ xref_id = match.group("xref")
322
+ tag = match.group("tag")
323
+ value = match.group("value")
324
+ if value == 'None': value = None
325
+ xref_value = value.strip("@") if value and XREF_RE.match(value.strip()) else None
326
+
327
+ return level, xref_id, tag, value, xref_value
268
328
  extension = '.ged'
269
329
 
270
330
  if not os.path.exists(filepath):
@@ -280,6 +340,7 @@ class Gedcom():
280
340
 
281
341
  records = []
282
342
  record_map = {0: None, 1: None, 2: None, 3: None, 4: None, 5: None}
343
+
283
344
  for l, line in enumerate(lines):
284
345
  if line.startswith(BOM):
285
346
  line = line.lstrip(BOM)
@@ -295,18 +356,27 @@ class Gedcom():
295
356
  if len(parts) == 3:
296
357
  level, col2, col3 = parts
297
358
 
298
- if col3 in Gedcom.top_level_tags:
359
+ if col3 in Gedcom._top_level_tags:
299
360
  tag = col3
300
361
  value = col2
301
362
  else:
302
363
  tag = col2
303
364
  value = col3
365
+
304
366
  else:
305
367
  level, tag = parts
306
368
 
369
+ level, xref, tag, value, xref_value = parse_gedcom7_line(line)
370
+
371
+ if xref is None and xref_value is not None:
372
+ xref = xref_value
373
+ # print(l, level, xref, tag, value, xref_value)
374
+
307
375
  level = int(level)
308
376
 
309
- new_record = GedcomRecord(line_num=l + 1, level=level, tag=tag, value=value)
377
+ new_record = GedcomRecord(line_num=l + 1, level=level, tag=tag, xref=xref,value=value)
378
+
379
+
310
380
  if level == 0:
311
381
  records.append(new_record)
312
382
  else:
@@ -315,6 +385,7 @@ class Gedcom():
315
385
  record_map[int(level) - 1].addSubRecord(new_record)
316
386
  record_map[int(level)] = new_record
317
387
 
388
+
318
389
  return records if records else None
319
390
 
320
391
  @staticmethod
@@ -329,17 +400,20 @@ class Gedcom():
329
400
  Gedcom: An instance of the Gedcom class.
330
401
  """
331
402
  records = Gedcom._records_from_file(filepath)
403
+
332
404
  gedcom = Gedcom(records=records)
333
405
 
334
406
  return gedcom
407
+
408
+ def merge_with_file(self, file_path: str) -> bool:
409
+ """
410
+ Adds records from a valid (``*``.ged) file to the current Genealogy
411
+
412
+ Args:
413
+ filepath (str): The path to the GEDCOM file.
414
+
415
+ Returns:
416
+ bool: Indicates if merge was successful.
417
+ """
418
+ return True
335
419
 
336
- #
337
- #import re
338
- #filepath = r"C:\Users\User\Documents\PythonProjects\gedcomx\.ged_files\_DJC_ Nunda Cartwright Family.ged"
339
- #with open(filepath, 'r', encoding='utf-8') as file:
340
- # string = file.read()
341
- #
342
- #for match in re.finditer(line, string):
343
- # data = match.groupdict()
344
- # print(data)
345
- #'''