gedcom-x 0.5.1__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"
@@ -77,7 +91,7 @@ class GedcomRecord():
77
91
 
78
92
  def describe(self,subRecords: bool = False):
79
93
  level_str = '\t'* self.level
80
- description = f"Line {self.line_num}: {level_str} Level: {self.level}, tag: '{self.tag}', value: '{self.value}', subRecords: {len(self._subRecords)}"
94
+ description = f"Line {self.line}: {level_str} Level: {self.level}, tag: '{self.tag}', xref={self.xref} value: '{self.value}', subRecords: {len(self._subRecords)}"
81
95
  if subRecords:
82
96
  for subRecord in self.subRecords():
83
97
  description = description + '\n' + subRecord.describe(subRecords=True)
@@ -128,13 +142,25 @@ class GedcomRecord():
128
142
  yield from self._flatten_subrecords(subrecord)
129
143
 
130
144
  class Gedcom():
131
- top_level_tags = ['INDI', 'FAM', 'OBJE', 'SOUR', 'REPO', 'NOTE', 'HEAD']
132
-
133
- # =========================================================
134
- # 1. INITIALIZATION
135
- # =========================================================
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.
136
154
 
137
-
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
+
138
164
  def __init__(self, records: Optional[List[GedcomRecord]] = None,filepath: str = None) -> None:
139
165
  if filepath:
140
166
  self.records = self._records_from_file(filepath)
@@ -142,34 +168,42 @@ class Gedcom():
142
168
  self.records: List[GedcomRecord] = records if records else []
143
169
 
144
170
 
171
+
145
172
  self._sources = []
146
173
  self._repositories = []
147
174
  self._individuals = []
148
175
  self._families = []
149
176
  self._objects = []
177
+ self._snotes = []
150
178
 
151
179
  if self.records:
152
180
  for record in self.records:
153
181
  if record.tag == 'INDI':
154
- record.xref = record.value
182
+
155
183
  self._individuals.append(record)
156
184
  if record.tag == 'SOUR' and record.level == 0:
157
- record.xref = record.value
185
+
158
186
  self._sources.append(record)
159
187
  if record.tag == 'REPO' and record.level == 0:
160
- record.xref = record.value
188
+ print(record.describe())
189
+
161
190
  self._repositories.append(record)
162
191
  if record.tag == 'FAM' and record.level == 0:
163
- record.xref = record.value
192
+
164
193
  self._families.append(record)
165
194
  if record.tag == 'OBJE' and record.level == 0:
166
- record.xref = record.value
195
+
167
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)
168
201
 
202
+
169
203
  # =========================================================
170
204
  # 2. PROPERTY ACCESSORS (GETTERS & SETTERS)
171
205
  # =========================================================
172
-
206
+
173
207
  @property
174
208
  def json(self):
175
209
  import json
@@ -216,6 +250,9 @@ class Gedcom():
216
250
 
217
251
  @property
218
252
  def repositories(self) -> List[GedcomRecord]:
253
+ """
254
+ List of **REPO** records found in the Genealogy
255
+ """
219
256
  return self._repositories
220
257
 
221
258
  @repositories.setter
@@ -254,18 +291,40 @@ class Gedcom():
254
291
  raise ValueError("objects must be a list of GedcomRecord objects.")
255
292
  self._objects = value
256
293
 
257
- # =========================================================
258
- # 3. METHODS
259
- # =========================================================
294
+
260
295
 
261
- def write(self):
296
+ def write(self) -> bool:
262
297
  """
263
298
  Method placeholder for writing GEDCOM files.
299
+
300
+ Raises
301
+ ------
302
+ NotImplementedError
303
+ writing to legacy GEDCOM file is not currently implimented.
264
304
  """
265
305
  raise NotImplementedError("Writing of GEDCOM files is not implemented.")
266
306
 
267
307
  @staticmethod
268
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
269
328
  extension = '.ged'
270
329
 
271
330
  if not os.path.exists(filepath):
@@ -281,6 +340,7 @@ class Gedcom():
281
340
 
282
341
  records = []
283
342
  record_map = {0: None, 1: None, 2: None, 3: None, 4: None, 5: None}
343
+
284
344
  for l, line in enumerate(lines):
285
345
  if line.startswith(BOM):
286
346
  line = line.lstrip(BOM)
@@ -296,18 +356,27 @@ class Gedcom():
296
356
  if len(parts) == 3:
297
357
  level, col2, col3 = parts
298
358
 
299
- if col3 in Gedcom.top_level_tags:
359
+ if col3 in Gedcom._top_level_tags:
300
360
  tag = col3
301
361
  value = col2
302
362
  else:
303
363
  tag = col2
304
364
  value = col3
365
+
305
366
  else:
306
367
  level, tag = parts
307
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
+
308
375
  level = int(level)
309
376
 
310
- 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
+
311
380
  if level == 0:
312
381
  records.append(new_record)
313
382
  else:
@@ -316,6 +385,7 @@ class Gedcom():
316
385
  record_map[int(level) - 1].addSubRecord(new_record)
317
386
  record_map[int(level)] = new_record
318
387
 
388
+
319
389
  return records if records else None
320
390
 
321
391
  @staticmethod
@@ -330,17 +400,20 @@ class Gedcom():
330
400
  Gedcom: An instance of the Gedcom class.
331
401
  """
332
402
  records = Gedcom._records_from_file(filepath)
403
+
333
404
  gedcom = Gedcom(records=records)
334
405
 
335
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
336
419
 
337
- #
338
- #import re
339
- #filepath = r"C:\Users\User\Documents\PythonProjects\gedcomx\.ged_files\_DJC_ Nunda Cartwright Family.ged"
340
- #with open(filepath, 'r', encoding='utf-8') as file:
341
- # string = file.read()
342
- #
343
- #for match in re.finditer(line, string):
344
- # data = match.groupdict()
345
- # print(data)
346
- #'''