- Files not closed - Note text set to 'list' instead of 'str' types - Address structure elements set to 'None' instead of '' - Long text strings losing characters and getting corrupted
1833 lines
77 KiB
Python
1833 lines
77 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2008-2011 Kees Bakker
|
|
# Copyright (C) 2008 Brian G. Matherly
|
|
# Copyright (C) 2013-2017 Alois Poettker <alois.poettker@gmx.de>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
|
|
"Import from Pro-Gen"
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# standard python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import re
|
|
import os
|
|
import struct
|
|
import sys
|
|
import time
|
|
|
|
#------------------------------------------------------------------------
|
|
#
|
|
# Set up logging
|
|
#
|
|
#------------------------------------------------------------------------
|
|
import logging
|
|
LOG = logging.getLogger('.ImportProGen')
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Gramps modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
|
_ = glocale.translation.gettext
|
|
from gramps.gen.config import config
|
|
from gramps.gen.datehandler import displayer
|
|
from gramps.gen.db import DbTxn
|
|
from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef, Citation,
|
|
Date, Event, EventRef, EventType, Family, FamilyRelType,
|
|
Name, NameType, NameOriginType, Note, NoteType, Person,
|
|
Place, PlaceName, Source, SrcAttribute, Surname, Tag)
|
|
from gramps.gen.utils.id import create_id
|
|
from gramps.gen.updatecallback import UpdateCallback
|
|
from gramps.gen.utils.libformatting import ImportInfo
|
|
|
|
class ProgenError(Exception):
|
|
"""
|
|
Error used to report Pro-Gen errors.
|
|
"""
|
|
def __init__(self, value=""):
|
|
Exception.__init__(self)
|
|
self.value = value
|
|
|
|
def __str__(self):
|
|
return self.value
|
|
|
|
def _importData(database, filename, user):
|
|
"""
|
|
Imports the files corresponding to the specified Database, Filename & User.
|
|
"""
|
|
try:
|
|
data = ProgenParser(database, filename, user)
|
|
except IOError as msg:
|
|
user.notify_error(_("%s could not be opened") % filename, str(msg))
|
|
return
|
|
|
|
try:
|
|
data.parse_progen_file()
|
|
except ProgenError as msg:
|
|
user.notify_error(_("Pro-Gen data error"), str(msg))
|
|
return
|
|
except IOError as msg:
|
|
user.notify_error(_("%s could not be opened") % filename, str(msg))
|
|
return
|
|
return ImportInfo({_("Results"): _("done")})
|
|
|
|
def _find_from_handle(progen_id, table):
|
|
"""
|
|
Find a handle corresponding to the specified Pro-Gen ID.
|
|
"""
|
|
# The passed table contains the mapping. If the value is found, we return
|
|
# it, otherwise we create a new handle, store it, and return it.
|
|
intid = table.get(progen_id)
|
|
if not intid:
|
|
intid = create_id()
|
|
table[progen_id] = intid
|
|
|
|
return intid
|
|
|
|
def _read_mem(bname):
|
|
"""
|
|
Read a Pro-Gen record.
|
|
"""
|
|
# Each record is 32 bytes. First a 4 byte reference to the next record
|
|
# followed by 28 bytes of text. The information forms a chain of records,
|
|
# that stops when a reference is 0 or smaller. There are two special
|
|
# sequences:
|
|
# <ESC> <CR> hard return
|
|
# <ESC> <^Z> end of the memo field
|
|
if os.path.exists(bname + '.MEM'):
|
|
fname = bname + '.MEM'
|
|
else:
|
|
fname = bname + '.mem'
|
|
|
|
with open(fname, "rb") as file_:
|
|
LOG.debug("The current system is %s-endian", sys.byteorder)
|
|
|
|
# The input file comes from [what was originally] a DOS machine so will
|
|
# be little-endian, regardless of the 'native' byte order of the host
|
|
recfmt = "<i28s"
|
|
reclen = struct.calcsize(str(recfmt))
|
|
# print("# reclen = %d" % reclen)
|
|
|
|
mems = []
|
|
while 1:
|
|
buf = file_.read(reclen)
|
|
if not buf:
|
|
break
|
|
(recno, text) = struct.unpack(recfmt, buf)
|
|
mems.append([recno, text])
|
|
|
|
return mems
|
|
|
|
def _read_recs(table, bname):
|
|
"""
|
|
Read records from .PER or .REL file.
|
|
"""
|
|
if os.path.exists(bname + table.fileext):
|
|
fname = bname + table.fileext
|
|
else:
|
|
fname = bname + table.fileext.lower()
|
|
|
|
with open(fname, "rb") as file_:
|
|
recfmt = table.recfmt
|
|
LOG.info("# %s - recfmt = %s" % (table['name1'], recfmt))
|
|
reclen = struct.calcsize(str(recfmt))
|
|
LOG.info("# %s - reclen = %d" % (table['name1'], reclen))
|
|
|
|
recs = []
|
|
while 1:
|
|
buf = file_.read(reclen)
|
|
if not buf:
|
|
break
|
|
tups = struct.unpack(recfmt, buf)
|
|
recs.append(tups)
|
|
|
|
LOG.info("# length %s.recs[] = %d" % (table['name1'], len(recs)))
|
|
|
|
return recs
|
|
|
|
def _get_defname(fname):
|
|
"""
|
|
Get the name of the PG30.DEF file by looking at the user DEF file.
|
|
"""
|
|
# And return the name of the DEF file. <fname> is expected to be somewhere in
|
|
# the PG30 tree. Contents of <fname> is something like:
|
|
# => \\0
|
|
# => C:\\PG30\\NL\\PG30-1.DEF
|
|
|
|
# We will strip the C: and convert the rest to a native pathname. Next, this
|
|
# pathname is compared with <fname>.
|
|
|
|
with open(fname) as file_:
|
|
lines = file_.readlines()
|
|
if not lines[0].startswith(r'\0') or len(lines) < 2:
|
|
raise ProgenError(_("Not a Pro-Gen file"))
|
|
|
|
defname = lines[1]
|
|
defname = defname.strip()
|
|
# Strip drive, if any
|
|
defname = re.sub(r'^\w:', '', defname)
|
|
defname = defname.replace('\\', os.sep)
|
|
# Strip leading slash, if any.
|
|
if defname.startswith(os.sep):
|
|
defname = defname[1:]
|
|
# LOG.warning('_get_defname: fname=%(fname)s => defname=%(defname)s', vars())
|
|
|
|
# Using the directory of <fname>, go to parent directory until the DEF is found.
|
|
dir_, file_ = os.path.split(os.path.abspath(fname))
|
|
while dir_ and dir_ != os.sep:
|
|
# LOG.warning('_get_defname: dir=%(dir_)s => defname=%(defname)s', vars())
|
|
newdefname = os.path.join(dir_, defname)
|
|
|
|
if os.path.exists(newdefname):
|
|
return newdefname, defname
|
|
newdefname = newdefname.lower()
|
|
if os.path.exists(newdefname):
|
|
return newdefname, defname
|
|
|
|
# One level up
|
|
dir_, file_ = os.path.split(dir_)
|
|
|
|
return None, defname
|
|
|
|
ESC_CTRLZ = re.compile(r'\033\032.*')
|
|
def _get_mem_text(mems, i):
|
|
"""Normalize text."""
|
|
# Notice that Pro-Gen starts the mem numbering at 1.
|
|
if i <= 0:
|
|
return
|
|
|
|
i -= 1
|
|
recno = mems[i][0] - 1
|
|
text = mems[i][1].decode('cp850')
|
|
while recno >= 0:
|
|
text += mems[recno][1].decode('cp850')
|
|
recno = mems[recno][0] - 1
|
|
|
|
text = text.replace('\033\r', '\n') # ESC-^M is newline
|
|
text = ESC_CTRLZ.sub('', text) # ESC-^Z is end of string
|
|
text = text.replace('\0', '') # There can be nul bytes. Remove them.
|
|
text = text.strip() # Strip leading/trailing whitespace
|
|
|
|
return text
|
|
|
|
MONTHES = {
|
|
'jan' : 1, # de, en, nl
|
|
'feb' : 2, 'febr' : 2, # de, en, nl
|
|
'mrz' : 3, # de
|
|
'mar' : 3, 'march' : 3, # en
|
|
'maa' : 3, 'mrt' : 3, 'maart' : 3, # nl
|
|
'apr' : 4, 'april' : 4, # de, en, nl
|
|
'mai' : 5, 'may' : 5, 'mei' : 5, # de, en, nl
|
|
'jun' : 6, 'june' : 6, 'juni' : 6, # de, en, nl
|
|
'jul' : 7, 'july' : 7, 'juli' : 7, # de, en, nl
|
|
'aug' : 8, # de, en, nl
|
|
'sep' : 9, 'sept' : 9, # de, en, nl
|
|
'okt' : 10, 'oct' : 10, 'ok' : 10, # de, en, nl
|
|
'nov' : 11, # de, en, nl
|
|
'dez' : 12, # de, nl
|
|
'dec' : 12, # en
|
|
}
|
|
def _cnv_month_to_int(mnth):
|
|
"""Converts monthnames to integer."""
|
|
|
|
return MONTHES.get(mnth, 0)
|
|
|
|
# Split surname prefixes
|
|
PREFIXES = [
|
|
't ', # nl
|
|
'den ', 'der ', 'de ', # de, nl
|
|
'het ', # nl
|
|
'in den ', # nl
|
|
'ten ', 'ter ', 'te ', # nl
|
|
'von ', 'von der ', # de
|
|
'van ', 'van den ', 'van der ', 'van de ', # nl
|
|
'zu ' # DE
|
|
]
|
|
def _split_surname(surname):
|
|
"""Divides prefix from surname."""
|
|
|
|
for prefix in PREFIXES:
|
|
if surname.startswith(prefix):
|
|
return prefix.strip(), surname[len(prefix):].strip()
|
|
|
|
return '', surname
|
|
|
|
|
|
# Example field:
|
|
# ['First name', '47', '64', '4', '2', '15', '""', '""']
|
|
# item 0
|
|
# item 1 is a number indicating the fieldtype
|
|
# item 2
|
|
# item 3 is the size of the field
|
|
class PG30DefTableField(object):
|
|
"""
|
|
This class represents a field in one of the tables in the DEF file.
|
|
"""
|
|
def __init__(self, name, value):
|
|
self.fieldname = name
|
|
self.fields = value.split(',')
|
|
self.fields = [p.strip() for p in self.fields]
|
|
# We have seen some case insensitivity in DEF files ...
|
|
self.name = self.fields[0].lower()
|
|
self.type_ = int(self.fields[1])
|
|
self.size = int(self.fields[3])
|
|
|
|
def __repr__(self):
|
|
return self.fieldname + ' -> ' + ', '.join(self.fields)
|
|
|
|
|
|
class PG30DefTable(object):
|
|
"""
|
|
This class represents a table in the DEF file.
|
|
"""
|
|
def __init__(self, name, lines):
|
|
self.name = name
|
|
self.flds = []
|
|
self.parms = {}
|
|
self.recfmt = None
|
|
|
|
# Example line:
|
|
# f02=Person_last_change ,32,10,10, 1,68,"","INDI CHAN DATE"
|
|
line_pat = re.compile(r'(\w+) = (.*)', re.VERBOSE)
|
|
for lne in lines:
|
|
mtch = line_pat.match(lne)
|
|
if mtch:
|
|
# TODO. Catch duplicates?
|
|
self.parms[mtch.group(1)] = mtch.group(2)
|
|
|
|
self.fileext = self.parms.get('fileext', None)
|
|
|
|
# If there is a n_fields entry then this is a table that
|
|
# has details about the record format of another file (PER or REL).
|
|
if 'n_fields' in self.parms:
|
|
self.get_fields()
|
|
self.recfmt = self.get_recfmt()
|
|
self.nam2fld = {}
|
|
self.nam2idx = {}
|
|
self.recflds = [] # list of fields that use up space in a record
|
|
j = 0
|
|
for fld in enumerate(self.flds):
|
|
# print("# field %s" % fld)
|
|
nam = fld[1].name
|
|
self.nam2fld[nam] = fld
|
|
# fld.size == 0: Field will not be acknowleged!
|
|
if fld[1].size != 0:
|
|
self.nam2idx[nam] = j
|
|
# print("# %s <= %d" % (fld.fieldname, j))
|
|
self.recflds.append(fld[1])
|
|
j += 1
|
|
|
|
def __getitem__(self, i):
|
|
return self.parms.get(i, None)
|
|
|
|
def get_recfmt(self):
|
|
"""Get the record format for struct.unpack"""
|
|
# Example field:
|
|
# ['First Name', '47', '64', '4', '2', '15', '""', '""']
|
|
# item 0
|
|
# item 1 is a number indicating the fieldtype
|
|
# item 2
|
|
# item 3 is the size of the field
|
|
# ...
|
|
|
|
flds = self.flds
|
|
# The input file comes from [what was originally] a DOS machine so will
|
|
# be little-endian, regardless of 'native' byte order of the host system
|
|
fmt = '<'
|
|
for fld in flds:
|
|
fldtyp = fld.type_
|
|
if fldtyp == 2 or fldtyp == 3 or fldtyp == 22 or fldtyp == 23:
|
|
fmt += 'i'
|
|
elif fldtyp == 31:
|
|
pass
|
|
elif fldtyp == 32 or fldtyp == 44 or fldtyp == 45:
|
|
fmt += '%ds' % fld.size
|
|
elif fldtyp == 41:
|
|
fmt += 'h'
|
|
elif fldtyp == 42 or fldtyp == 43 or fldtyp == 46 or fldtyp == 47:
|
|
fmt += 'i'
|
|
else:
|
|
pass # ???? Do we want to know?
|
|
|
|
return fmt
|
|
|
|
def get_fields(self):
|
|
"""Get the fields."""
|
|
# For example from PG30-1.DEF
|
|
# n_fields=58
|
|
# f01=Person ID , 31, 6, 0, 1, 17, "", "INDI RFN"
|
|
# f02=Person change, 32, 10,10, 1, 68, "", "INDI CHAN DATE"
|
|
# f03=First name , 47, 64, 4, 2, 15, "", ""
|
|
|
|
n_fields = int(self.parms['n_fields'])
|
|
flds = []
|
|
for i in range(n_fields):
|
|
fld_name = 'f%02d' % (i+1)
|
|
fld = self.parms.get(fld_name, None)
|
|
flds.append(PG30DefTableField(fld_name, fld))
|
|
|
|
self.flds = flds
|
|
|
|
def get_record_field_index(self, fldname):
|
|
"""Return the index number in the record tuple, based on the name."""
|
|
|
|
if not fldname in self.nam2idx:
|
|
raise ProgenError(_("Field '%(fldname)s' not found") % locals())
|
|
|
|
return self.nam2idx[fldname]
|
|
|
|
def convert_record_to_list(self, rec, mems):
|
|
"""Convert records to list."""
|
|
|
|
flds = []
|
|
for i in range(len(rec)):
|
|
typ = self.recflds[i].type_
|
|
if typ == 2 or typ == 3 or typ == 22 or typ == 23:
|
|
# Record field is record number
|
|
flds.append("%d" % rec[i])
|
|
elif typ == 46 or typ == 47:
|
|
# Record field is memory type
|
|
flds.append(_get_mem_text(mems, rec[i]))
|
|
else:
|
|
# Not a record number, not a memory type. It must be just text.
|
|
fld = rec[i].strip()
|
|
fld = fld.decode('cp850') # Convert to unicode
|
|
flds.append(fld)
|
|
|
|
# print(', '.join(flds))
|
|
return flds
|
|
|
|
def get_field_names(self):
|
|
"""Return field names."""
|
|
|
|
ret = []
|
|
for fld in self.flds:
|
|
if fld.size != 0:
|
|
ret.append(fld.name)
|
|
|
|
return ret
|
|
|
|
def diag(self):
|
|
"""Diagnostic ..."""
|
|
|
|
txt = self.name + '\n'
|
|
if 'n_fields' in self.parms:
|
|
txt += 'n_fields = %s\n' % self.parms['n_fields']
|
|
# Just grab a field
|
|
txt += '"%s"\n' % self.flds[1]
|
|
txt += 'recfmt = %s (length=%d)' % \
|
|
(self.recfmt, struct.calcsize(str(self.recfmt)))
|
|
|
|
return txt
|
|
|
|
|
|
class PG30Def(object):
|
|
"""
|
|
Utility class to read PG30-1.DEF and to get certain information from it.
|
|
"""
|
|
# The contents of the DEF file is separated in sections that start
|
|
# with [<section name>]. For example:
|
|
# [general]
|
|
# dateformat=DD-MM-YYYY
|
|
# pointerlength=4
|
|
# tables=2
|
|
|
|
def __init__(self, fname):
|
|
# print(fname)
|
|
fname, dname = _get_defname(fname)
|
|
if not fname:
|
|
raise ProgenError(_("Cannot find DEF file: %(dname)s") % locals())
|
|
|
|
# Read the DEF file (maybe throw a IOError)
|
|
lines = None
|
|
with open(fname, buffering=1,
|
|
encoding='cp437', errors='strict') as frme:
|
|
lines = frme.readlines()
|
|
|
|
# Analyse the DEF lines
|
|
lines = [l.strip() for l in lines]
|
|
content = '\n'.join(lines)
|
|
parts = re.split(r'\n(?=\[)', content)
|
|
self.parts = {}
|
|
self.tables = {}
|
|
for prts in parts:
|
|
lines = prts.splitlines()
|
|
|
|
# Get section names (typically "PRO-GEN", "general",
|
|
# "Table_1", "Table_2", "Genealogical")
|
|
k = re.sub(r'\[(.*)\]', r'\1', lines[0])
|
|
|
|
# Store section contents in a hashtable using that section name
|
|
self.parts[k] = lines[1:]
|
|
self.tables[k] = PG30DefTable(k, self.parts[k])
|
|
|
|
def __getitem__(self, i):
|
|
return self.tables.get(i, None)
|
|
|
|
def __repr__(self):
|
|
return '\n'.join([self.tables[t].diag() for t in self.tables])
|
|
|
|
|
|
TAGOBJECTS = ['Person', 'Family', 'Event', 'Place', 'Citation', 'Source', 'Note']
|
|
class ProgenParser(UpdateCallback):
|
|
"""
|
|
Main class to import and parse Pro-Gen files.
|
|
"""
|
|
|
|
def parse_progen_file(self):
|
|
"""
|
|
Parse and analyse the Pro-Gen file.
|
|
"""
|
|
self.def_ = PG30Def(self.fname)
|
|
|
|
# Check correct languages (only 'de', 'en' and 'nl' accepted)
|
|
male_text = self.def_.tables['Genealogical'].parms['field_father'].lower()
|
|
female_text = self.def_.tables['Genealogical'].parms['field_mother'].lower()
|
|
# Double check on keywords
|
|
if male_text == "vater" and female_text == "mutter":
|
|
self.language = 0 # language = 'de'
|
|
elif male_text == "father" and female_text == "mother":
|
|
self.language = 1 # language = 'en'
|
|
elif male_text == "vader" and female_text == "moeder":
|
|
self.language = 2 # language = 'nl'
|
|
else:
|
|
# Raise a error message
|
|
error_msg = ProgenError(_("Not a supported Pro-Gen import file language"))
|
|
self.user.notify_error(_("Pro-Gen data error"), str(error_msg))
|
|
return
|
|
|
|
self.mems = _read_mem(self.bname)
|
|
self.pers = _read_recs(self.def_['Table_1'], self.bname)
|
|
self.rels = _read_recs(self.def_['Table_2'], self.bname)
|
|
|
|
self.set_total(2 * len(self.pers) + len(self.rels))
|
|
# self.reset(_("Import from Pro-Gen")) # non-functional for now
|
|
|
|
with DbTxn(_("Pro-Gen import"), self.dbase, batch=True) as self.trans:
|
|
self.dbase.disable_signals()
|
|
|
|
self.create_tags()
|
|
self.create_persons()
|
|
self.create_families()
|
|
self.add_children()
|
|
|
|
self.dbase.enable_signals()
|
|
self.dbase.request_rebuild()
|
|
|
|
def __init__(self, data_base, file_name, user):
|
|
"""
|
|
Pro-Gen defines his own set of (static) person and family identifiers.
|
|
"""
|
|
UpdateCallback.__init__(self, user.callback)
|
|
# Sometime their match the GRAMPS localisation, sometimes not. To be on
|
|
# a safe and uniform path person and family identifiers for (alphabetical)
|
|
# German (de), English (en) and Dutch (nl) language defined here.
|
|
self.bname, ext = os.path.splitext(file_name)
|
|
if ext.lower() in ('.per', '.rel', '.mem'):
|
|
file_name = self.bname + '.def'
|
|
self.dbase = data_base
|
|
self.fname = file_name
|
|
self.user = user
|
|
self.language = 0
|
|
|
|
self.mems = None # Memory area
|
|
self.pers = [] # List for raw person data
|
|
self.rels = [] # List for raw relation data
|
|
|
|
self.gid2id = {} # Maps person id to id
|
|
self.fid2id = {} # Maps family id to id
|
|
self.fm2fam = {} # Maps family id t0 family
|
|
self.pkeys = {} # Caching place handles
|
|
self.skeys = {} # Caching source handles
|
|
self.ckeys = {} # Caching citation handles
|
|
|
|
# Additonal options to for data import
|
|
self.opt_ind_id = -1 # Individual ID start (-1 keeps Original IDs)
|
|
self.opt_fam_id = -1 # Family ID start (-1 keeps Original IDs)
|
|
self.opt_relation_code = 0 # Relation code contains one/two letters
|
|
self.opt_birth_time = False # Birth time in description
|
|
self.opt_death_time = False # Death time in description
|
|
self.opt_death_info2cause = True # Death info to Death cause
|
|
|
|
# Miscalaneous
|
|
self.trans = None # Transaction identifier
|
|
self.def_ = None # PG30 definitions
|
|
self.high_fam_id = -1
|
|
|
|
# Add Config import tag?
|
|
self.tagobject_list = {}
|
|
|
|
# Add Config import source?
|
|
self.source_title, self.citation_page, self.citation_attr = '', '', ''
|
|
self.default_source = config.get('preferences.default-source')
|
|
if self.default_source:
|
|
fname = os.path.basename(file_name).split('\\')[-1]
|
|
self.source_title = _("Import from Pro-Gen (%s)") % fname
|
|
self.citation_page = "Pro-Gen Import %s" % time.strftime("%Y-%m-%d")
|
|
self.citation_attr = "Pro-Gen Import (%s) %s" % (fname, time.strftime("%Y-%m-%d"))
|
|
|
|
# Records in the PER file using PG30-1.DEF contain the following fields:
|
|
self.person_identifier = [
|
|
# F00: None
|
|
["", ""], # F00
|
|
|
|
# F01 - F15: Person ID, Change, First / Last Name, Gender, Call Name,
|
|
# Alias, Person Code, Titel 1/2/3, Father, Mother, Occupation
|
|
["Person_ID", "Person_record", "Persoon record"], # F01
|
|
["Person_Änderung", "Person_last_change", "Persoon gewijzigd"], # F02
|
|
["Vorname", "Given_name", "Voornaam"], # F03
|
|
["Nachname", "Surname", "Achternaam"], # F04
|
|
["Geschlecht", "Sex", "Geslacht"], # F05
|
|
["Patronym", "Patronym", "Patroniem"], # F06
|
|
["Rufname", "Call_name", "Roepnaam"], # F07
|
|
["Alias", "Alias", "Alias"], # F08
|
|
["Person_Code", "Person_code", "Persoon code"], # F09
|
|
["Titel1", "Title1", "Titel1"], # F10
|
|
["Titel2", "Title2", "Titel2"], # F11
|
|
["Titel3", "Title3", "Titel3"], # F12
|
|
["Vater", "Father", "Vader"], # F13
|
|
["Mutter", "Mother", "Moeder"], # F14
|
|
["Beruf", "Occupation", "Beroep"], # F15
|
|
|
|
# F15 - F17: Person Note, Info
|
|
["Person_Notiz", "Person_scratch", "Persoon klad"], # F16
|
|
["Person_Info", "Person_info", "Persoon info"], # F17
|
|
|
|
# F18 - F24: Address Date, Street, ZIP, Place, Country, Phone, Info
|
|
["Anschrift_Datum", "Address_date", "Adres datum"], # F18
|
|
["Anschrift_Straße", "Address_street", "Adres straat"], # F19
|
|
["Anschrift_PLZ", "Address_zip", "Adres postcode"], # F20
|
|
["Anschrift_Ort", "Address_place", "Adres plaats"], # F21
|
|
["Anschrift_Land", "Address_country", "Adres land"], # F22
|
|
["Anschrift_Telefon", "Address_phone", "Adres telefoon"], #
|
|
["Anschrift_Info", "Address_info", "Adres info"], # F24
|
|
|
|
# F25 - F31: Birth Date, Place, Time, Source, Reference, Text, Info
|
|
["Geburt_Datum", "Birth_date", "Geboorte datum"], # F25
|
|
["Geburt_Ort", "Birth_place", "Geboorte plaats"], # F26
|
|
["Geburt_Zeit", "Birth_time", "Geboorte tijd"], # F27
|
|
["Geburt_Quelle", "Birth_source", "Geboorte bron"], # F28
|
|
["Geburt_Akte", "Birth_ref", "Geboorte akte"], # F29
|
|
["Geburt_Text", "Birth_text", "Geboorte brontekst"], # F30
|
|
["Geburt_Info", "Birth_info", "Geboorte info"], # F31
|
|
|
|
# F32 - F39: Christening Date, Place, Religion, Witness, Source,
|
|
# Reference, Text, Info
|
|
["Taufe_Datum", "Christening_date", "Doop datum"], # F32
|
|
["Taufe_Ort", "Christening_place", "Doop plaats"], # F33
|
|
["Religion", "Religion", "Gezindte"], # F34
|
|
["Taufe_Paten", "Christening_witness", "Doop getuigen"], # F35
|
|
["Taufe_Quelle", "Christening_source", "Doop bron"], # F36
|
|
["Taufe_Akte", "Christening_ref", "Doop akte"], # F37
|
|
["Taufe_Text", "Christening_text", "Doop brontekst"], # F38
|
|
["Taufe_Info", "Christening_info", "Doop info"], # F39
|
|
|
|
# F40 - F46: Death Date, Place, Time, Source, Reference, Text, Info
|
|
["Sterbe_Datum", "Death_date", "Overlijden datum"], # F40
|
|
["Sterbe_Ort", "Death_place", "Overlijden plaats"], # F41
|
|
["Sterbe_Zeit", "Death_time", "Overlijden tijd"], # F42
|
|
["Sterbe_Quelle", "Death_source", "Overlijden bron"], # F43
|
|
["Sterbe_Akte", "Death_ref", "Overlijden akte"], # F44
|
|
["Sterbe_Text", "Death_text", "Overlijden brontekst"], # F45
|
|
["Sterbe_Info", "Death_info", "Overlijden info"], # F46
|
|
|
|
# F47 - F52: Cremation Date, Place, Source, Reference, Text, Info
|
|
["Einäscherung_Datum", "Cremation_date", "Crematie datum"], # F47
|
|
["Einäscherung_Ort", "Cremation_place", "Crematie plaats"], # F48
|
|
["Einäscherung_Quelle", "Cremation_source", "Crematie bron"], # F49
|
|
["Einäscherung_Akte", "Cremation_ref", "Crematie akte"], # F50
|
|
["Einäscherung_Text", "Cremation_text", "Crematie brontekst"], # F51
|
|
["Einäscherung_Info", "Cremation_info", "Crematie info"], # F52
|
|
|
|
# F53 - F58: Burial Date, Place, Source, Reference, Text, Info
|
|
["Beerdigung_Datum", "Burial_date", "Begrafenis datum"], # F53
|
|
["Beerdigung_Ort", "Burial_place", "Begrafenis plaats"], # F54
|
|
["Beerdigung_Quelle", "Burial_source", "Begrafenis bron"], # F55
|
|
["Beerdigung_Akte", "Burial_ref", "Begrafenis akte"], # F56
|
|
["Beerdigung_Text", "Burial_text", "Begrafenis brontekst"], # F57
|
|
["Beerdigung_Info", "Burial_info", "Begrafenis info"], # F58
|
|
]
|
|
|
|
# Records in the REL file using PG30-1.DEF contain the following fields:
|
|
self.family_identifier = [
|
|
# F00: None
|
|
["", ""], # F00
|
|
|
|
# F01 - F07: Relation ID, Change, Husband, Wife, Code, Note, Info
|
|
["Ehe_ID", "Relation_record", "Voordat record"], # F01
|
|
["Ehe_Änderung", "Relation_last_change", "Voordat gewijzigd"], # F02
|
|
["Ehemann", "Husband", "Man"], # F03
|
|
["Ehefrau", "Wife", "Vrouw"], # F04
|
|
["Ehe_Code", "Relation_code", "Relatie code"], # F05
|
|
["Ehe_Notiz", "Relation_scratch", "Relatie klad"], # F06
|
|
["Ehe_Info", "Relation_info", "Relatie info"], # F07
|
|
|
|
# F08 - F13: Civil Union Date, Place, Source, Reference, Text, Info
|
|
["Lebensgem_Datum", "Living_date", "Samenwonen datum"], # F08
|
|
["Lebensgem_Ort", "Living_place", "Samenwonen plaats"], # F09
|
|
["Lebensgem_Quelle", "Living_source", "Samenwonen bron"], # F10
|
|
["Lebensgem_Akte", "Living_ref", "Samenwonen akte"], # F11
|
|
["Lebensgem_Text", "Living_text", "Samenwonen brontekst"], # F12
|
|
["Lebensgem_Info", "Living_info", "Samenwonen info"], # F13
|
|
|
|
# F14 - F20: Marriage License Date, Place, Witness, Source, Record,
|
|
# Text, Info
|
|
["Aufgebot_Datum", "Banns_date", "Ondertrouw datum"], # F14
|
|
["Aufgebot_Ort", "Banns_place", "Ondertrouw plaats"], # F15
|
|
["Aufgebot_Zeugen", "Banns_witnesses", "Ondertrouw getuigen"], # F16
|
|
["Aufgebot_Quelle", "Banns_source", "Ondertrouw bron"], # F17
|
|
["Aufgebot_Akte", "Banns_ref", "Ondertrouw akte"], # F18
|
|
["Aufgebot_Text", "Banns_text", "Ondertrouw brontekst"], # F19
|
|
["Aufgebot_Info", "Banns_info", "Ondertrouw info"], # F20
|
|
|
|
# F14 - F20: Civil Marriage Date, Place, Witness, Source, Record,
|
|
# Text, Info
|
|
["Standesamt_Datum", "Civil_date", "Wettelijk datum"], # F21
|
|
["Standesamt_Ort", "Civil_place", "Wettelijk plaats"], # F22
|
|
["Standesamt_Zeugen", "Civil_witnesses", "Wettelijk getuigen"], # F23
|
|
["Standesamt_Quelle", "Civil_source", "Wettelijk bron"], # F24
|
|
["Standesamt_Akte", "Civil_ref", "Wettelijk akte"], # F25
|
|
["Standesamt_Text", "Civil_text", "Wettelijk brontekst"], # F26
|
|
["Standesamt_Info", "Civil_info", "Wettelijk info"], # F27
|
|
|
|
# F28 - F35: Church Wedding Date, Place, Church Name, Witness,
|
|
# Source, Reference, Text, Info
|
|
["Kirche_Datum", "Church_date", "Kerkelijk datum"], # F28
|
|
["Kirche_Ort", "Church_place", "Kerkelijk plaats"], # F29
|
|
["Kirche", "Church", "Kerk"], # F30
|
|
["Kirche_Zeugen", "Church_witnesses", "Kerkelijk getuigen"], # F31
|
|
["Kirche_Quelle", "Church_source", "Kerkelijk bron"], # F32
|
|
["Kirche_Akte", "Church_ref", "Kerkelijk akte"], # F33
|
|
["Kirche_Text", "Church_text", "Kerkelijk brontekst"], # F34
|
|
["Kirche_Info", "Church_info", "Kerkelijk info"], # F35
|
|
|
|
# F36 - F41: Divorce Date, Place, Source, Reference, Text, Info
|
|
["Scheidung_Datum", "Divorce_date", "Scheiding datum"], # F36
|
|
["Scheidung_Ort", "Divorce_place", "Scheiding plaats"], # F37
|
|
["Scheidung_Quelle", "Divorce_source", "Scheiding bron"], # F38
|
|
["Scheidung_Akte", "Divorce_ref", "Scheiding akte"], # F39
|
|
["Scheidung_Text", "Divorce_text", "Scheiding brontekst"], # F40
|
|
["Scheidung_Info", "Divorce_info", "Scheiding info"], # F41
|
|
]
|
|
|
|
def __add_tag(self, name, obj):
|
|
"""
|
|
Add the default tag to the object.
|
|
"""
|
|
if self.tagobject_list and name in self.tagobject_list:
|
|
obj.add_tag(self.tagobject_list[name].handle)
|
|
|
|
def __find_person_handle(self, progen_id):
|
|
"""
|
|
Return the database handle associated with the person's Pro-Gen ID
|
|
"""
|
|
return _find_from_handle(progen_id, self.gid2id)
|
|
|
|
def __find_family_handle(self, progen_id):
|
|
"""
|
|
Return the database handle associated with the family's Pro-Gen ID
|
|
"""
|
|
return _find_from_handle(progen_id, self.fid2id)
|
|
|
|
def __find_or_create_person(self, progen_id):
|
|
"""
|
|
Finds or creates a Person based on the Pro-Gen ID.
|
|
"""
|
|
# If the ID is already used (= is in the database), we return the item in
|
|
# the DB. Otherwise, we create a new person, assign the handle and GRAMPS ID.
|
|
person = Person()
|
|
intid = self.gid2id.get(progen_id)
|
|
if self.dbase.has_person_handle(intid):
|
|
person.unserialize(self.dbase.get_raw_person_data(intid))
|
|
else:
|
|
# create a new Person
|
|
gramps_id = self.dbase.id2user_format("I%05d" % progen_id)
|
|
# make sure that gramps_id are bytes ...
|
|
if self.dbase.has_person_gramps_id(gramps_id.encode('utf-8')):
|
|
gramps_id = self.dbase.find_next_person_gramps_id()
|
|
intid = _find_from_handle(progen_id, self.gid2id)
|
|
person.set_handle(intid)
|
|
person.set_gramps_id(gramps_id)
|
|
|
|
return person
|
|
|
|
def __find_or_create_family(self, progen_id):
|
|
"""
|
|
Finds or creates a Family based on the Pro-Gen ID.
|
|
"""
|
|
family = Family()
|
|
intid = self.fid2id.get(progen_id)
|
|
if self.dbase.has_family_handle(intid):
|
|
family.unserialize(self.dbase.get_raw_family_data(intid))
|
|
else:
|
|
# create a new Family
|
|
gramps_id = self.dbase.fid2user_format("F%04d" % progen_id)
|
|
# make sure that gramps_id are bytes ...
|
|
if self.dbase.has_family_gramps_id(gramps_id.encode('utf-8')):
|
|
gramps_id = self.dbase.find_next_family_gramps_id()
|
|
intid = _find_from_handle(progen_id, self.fid2id)
|
|
family.set_handle(intid)
|
|
family.set_gramps_id(gramps_id)
|
|
|
|
return family
|
|
|
|
def __get_or_create_place(self, place_name):
|
|
"""
|
|
Finds or creates a Place based on the place name.
|
|
"""
|
|
if not place_name:
|
|
return None
|
|
|
|
if place_name in self.pkeys:
|
|
place = self.dbase.get_place_from_handle(self.pkeys[place_name])
|
|
else:
|
|
# create a new Place
|
|
place = Place()
|
|
place.set_name(PlaceName(value=place_name))
|
|
place.set_title(place_name)
|
|
self.__add_tag('Place', place) # add tag to 'Place' object
|
|
self.dbase.add_place(place, self.trans)
|
|
self.dbase.commit_place(place, self.trans)
|
|
|
|
self.pkeys[place_name] = place.get_handle()
|
|
|
|
return place
|
|
|
|
def __get_or_create_citation(self, source_title, date_text, citation_attr='', page_ref='',
|
|
confidence=2, note_text=None, attr_text=None):
|
|
"""
|
|
Finds or creates a Citation based on Source, Name, Date, Page, Note, Attribute.
|
|
"""
|
|
if not source_title:
|
|
return None
|
|
|
|
page = source_title
|
|
if citation_attr or page_ref:
|
|
page = '%s %s' % (citation_attr, page_ref)
|
|
|
|
# process Source
|
|
if source_title in self.skeys: # source exists
|
|
source = self.dbase.get_source_from_handle(self.skeys[source_title])
|
|
else: # create a new source
|
|
source = Source()
|
|
source.set_title(source_title)
|
|
|
|
self.__add_tag('Source', source) # add tag to 'Source' object
|
|
|
|
self.dbase.add_source(source, self.trans)
|
|
self.dbase.commit_source(source, self.trans)
|
|
|
|
self.skeys[source_title] = source.get_handle()
|
|
|
|
# process Citation
|
|
if page in self.ckeys: # citation exists
|
|
citation = self.dbase.get_citation_from_handle(self.ckeys[page])
|
|
else: # create a new citation
|
|
citation = Citation()
|
|
citation.set_reference_handle(source.get_handle())
|
|
self.__add_tag('Citation', citation) # add tag to 'Citation' object
|
|
self.dbase.add_citation(citation, self.trans)
|
|
|
|
self.ckeys[page] = citation.get_handle()
|
|
|
|
# process Date
|
|
date = self.__create_date_from_text(date_text)
|
|
if date:
|
|
citation.set_date_object(date)
|
|
|
|
# process Confidence
|
|
citation.set_confidence_level(confidence)
|
|
|
|
# process Page
|
|
citation.set_page('%s' % page)
|
|
|
|
# process Note
|
|
note = self.__create_note(note_text, NoteType.CUSTOM, "Pro-Gen Import")
|
|
if note and note.handle:
|
|
citation.add_note(note.handle)
|
|
|
|
# process Attribute
|
|
if attr_text:
|
|
sattr = SrcAttribute()
|
|
sattr.set_type(_("Source"))
|
|
sattr.set_value(attr_text)
|
|
citation.add_attribute(sattr)
|
|
|
|
self.dbase.commit_citation(citation, self.trans)
|
|
|
|
return citation
|
|
|
|
def __create_note(self, note_text, note_type, note_cust=''):
|
|
"""
|
|
Create an note base on Type and Text.
|
|
"""
|
|
if not note_text:
|
|
return None
|
|
|
|
if isinstance(note_text, list):
|
|
note_text = '\n'.join(note_text)
|
|
|
|
note = Note()
|
|
note.set(note_text)
|
|
note_type = NoteType()
|
|
note_type.set((note_type, note_cust))
|
|
|
|
self.__add_tag('Note', note) # add tag to 'Note' object
|
|
|
|
self.dbase.add_note(note, self.trans)
|
|
self.dbase.commit_note(note, self.trans)
|
|
|
|
return note
|
|
|
|
def __create_attribute(self, attr_text, attr_type, attr_cust=''):
|
|
"""
|
|
Creates an attribute base on (Custom-)Type and Text.
|
|
"""
|
|
if not attr_text:
|
|
return None
|
|
|
|
attr = Attribute()
|
|
attr.set_type((attr_type, attr_cust))
|
|
attr.set_value(attr_text)
|
|
|
|
return attr
|
|
|
|
def __create_event_and_ref(self, type_, desc=None, date=None, place=None,
|
|
citation=None, note_text=None,
|
|
attr_text=None, attr_type=None, attr_cust=None):
|
|
"""
|
|
Finds or creates an Event based on the Type, Description, Date, Place,
|
|
Citation, Note and Time.
|
|
"""
|
|
event = Event()
|
|
event.set_type(EventType(type_))
|
|
self.__add_tag('Event', event) # add tag to 'Event' object
|
|
|
|
if desc:
|
|
event.set_description(desc)
|
|
if date:
|
|
event.set_date_object(date)
|
|
if place:
|
|
event.set_place_handle(place.get_handle())
|
|
if citation:
|
|
event.add_citation(citation.handle)
|
|
|
|
attr = self.__create_attribute(attr_text, attr_type, attr_cust)
|
|
if attr:
|
|
event.add_attribute(attr)
|
|
|
|
note = self.__create_note(note_text, NoteType.CUSTOM, "Info")
|
|
if note and note.handle:
|
|
event.add_note(note.handle)
|
|
|
|
self.dbase.add_event(event, self.trans)
|
|
self.dbase.commit_event(event, self.trans)
|
|
|
|
event_ref = EventRef()
|
|
event_ref.set_reference_handle(event.get_handle())
|
|
|
|
return event, event_ref
|
|
|
|
__date_pat1 = re.compile(r'(?P<day>\d{1,2}) (.|-|=) (?P<month>\d{1,2}) (.|-|=) (?P<year>\d{2,4})',
|
|
re.VERBOSE)
|
|
__date_pat2 = re.compile(r'(?P<month>\d{1,2}) (.|-|=) (?P<year>\d{4})',
|
|
re.VERBOSE)
|
|
__date_pat3 = re.compile(r'(?P<year>\d{3,4})', re.VERBOSE)
|
|
__date_pat4_de = re.compile(r'(v|vor|n|nach|ca|circa|etwa|in|um|±) (\.|\s)* (?P<year>\d{3,4})',
|
|
re.VERBOSE)
|
|
__date_pat4_en = re.compile(r'(b|before|a|after|ab|about|between|±) (\.|\s)* (?P<year>\d{3,4})',
|
|
re.VERBOSE)
|
|
__date_pat4_nl = re.compile(r'(v|voor|vóór|na|ca|circa|rond|±) (\.|\s)* (?P<year>\d{3,4})',
|
|
re.VERBOSE)
|
|
__date_pat5 = re.compile(r'(oo|OO) (-|=) (oo|OO) (-|=) (?P<year>\d{2,4})',
|
|
re.VERBOSE)
|
|
__date_pat6 = re.compile(r'(?P<month>(%s)) (\.|\s)* (?P<year>\d{3,4})' % \
|
|
'|'.join(list(MONTHES.keys())),
|
|
re.VERBOSE | re.IGNORECASE)
|
|
|
|
def __create_date_from_text(self, date_text, diag_msg=None):
|
|
"""
|
|
Finds or creates a Date based on Text, an Offset and a Message.
|
|
"""
|
|
# Pro-Gen has a text field for the date.
|
|
# It can be anything (it should be dd-mm-yyyy), but we have seen:
|
|
# yyyy
|
|
# mm-yyyy
|
|
# before yyyy
|
|
# dd=mm-yyyy (typo I guess)
|
|
# 00-00-yyyy
|
|
# oo-oo-yyyy
|
|
# dd-mm-00 (does this mean we do not know about the year?)
|
|
|
|
# This function tries to parse the text and create a proper Gramps Date()
|
|
# object. If all else fails we create a MOD_TEXTONLY Date() object.
|
|
|
|
dte_txt = date_text == _("Unknown")
|
|
if not (dte_txt or date_text) or date_text == '??':
|
|
return None
|
|
|
|
date = Date()
|
|
|
|
# dd-mm-yyyy
|
|
dte_mtch = self.__date_pat1.match(date_text)
|
|
if dte_mtch:
|
|
day = int(dte_mtch.group('day'))
|
|
month = int(dte_mtch.group('month'))
|
|
if month > 12:
|
|
month %= 12
|
|
year = int(dte_mtch.group('year'))
|
|
if day and month and year:
|
|
date.set_yr_mon_day(year, month, day)
|
|
else:
|
|
date.set(Date.QUAL_NONE, Date.MOD_ABOUT, Date.CAL_GREGORIAN,
|
|
(day, month, year, 0))
|
|
return date
|
|
|
|
# mm-yyyy
|
|
dte_mtch = self.__date_pat2.match(date_text)
|
|
if dte_mtch:
|
|
month = int(dte_mtch.group('month'))
|
|
year = int(dte_mtch.group('year'))
|
|
date.set(Date.QUAL_NONE, Date.MOD_ABOUT, Date.CAL_GREGORIAN,
|
|
(0, month, year, 0))
|
|
return date
|
|
|
|
# yyy or yyyy
|
|
dte_mtch = self.__date_pat3.match(date_text)
|
|
if dte_mtch:
|
|
year = int(dte_mtch.group('year'))
|
|
date.set(Date.QUAL_NONE, Date.MOD_ABOUT, Date.CAL_GREGORIAN,
|
|
(0, 0, year, 0))
|
|
return date
|
|
|
|
# before|after|... yyyy
|
|
if self.language == 0: # 'de' language
|
|
dte_mtch = self.__date_pat4_de.match(date_text)
|
|
elif self.language == 1: # 'en' language
|
|
dte_mtch = self.__date_pat4_en.match(date_text)
|
|
elif self.language == 2: # 'nl' language
|
|
dte_mtch = self.__date_pat4_nl.match(date_text)
|
|
if dte_mtch:
|
|
year = int(dte_mtch.group('year'))
|
|
if dte_mtch.group(1) == 'v' or dte_mtch.group(1) == 'vor' or \
|
|
dte_mtch.group(1) == 'before' or \
|
|
dte_mtch.group(1) == 'voor' or dte_mtch.group(1) == 'vóór':
|
|
date.set(Date.QUAL_NONE, Date.MOD_BEFORE, Date.CAL_GREGORIAN,
|
|
(0, 0, year, 0))
|
|
elif dte_mtch.group(1) == 'n' or dte_mtch.group(1) == 'nach' or \
|
|
dte_mtch.group(1) == 'after' or \
|
|
dte_mtch.group(1) == 'na':
|
|
date.set(Date.QUAL_NONE, Date.MOD_AFTER, Date.CAL_GREGORIAN,
|
|
(0, 0, year, 0))
|
|
else:
|
|
date.set(Date.QUAL_NONE, Date.MOD_ABOUT, Date.CAL_GREGORIAN,
|
|
(0, 0, year, 0))
|
|
return date
|
|
|
|
# oo-oo-yyyy
|
|
dte_mtch = self.__date_pat5.match(date_text)
|
|
if dte_mtch:
|
|
year = int(dte_mtch.group('year'))
|
|
date.set(Date.QUAL_NONE, Date.MOD_ABOUT, Date.CAL_GREGORIAN,
|
|
(0, 0, year, 0))
|
|
return date
|
|
|
|
# mmm yyyy (textual month)
|
|
dte_mtch = self.__date_pat6.match(date_text)
|
|
if dte_mtch:
|
|
year = int(dte_mtch.group('year'))
|
|
month = _cnv_month_to_int(dte_mtch.group('month'))
|
|
date.set(Date.QUAL_NONE, Date.MOD_ABOUT, Date.CAL_GREGORIAN,
|
|
(0, month, year, 0))
|
|
return date
|
|
|
|
# Hmmm. Just use the plain text.
|
|
LOG.warning(_("Date did not match: '%(text)s' (%(msg)s)"), \
|
|
{'text' : date_text.encode('utf-8'), 'msg' : diag_msg or ''})
|
|
date.set_as_text(date_text)
|
|
|
|
return date
|
|
|
|
def __create_desc_from_text(self, desc_txt):
|
|
"""
|
|
Creates a variation of a description depending on language
|
|
"""
|
|
desc = None
|
|
if desc_txt:
|
|
if self.language == 0: # 'de' language
|
|
desc = desc_txt + " Uhr"
|
|
elif self.language == 1: # 'en' language
|
|
desc = "Time: " + desc_txt
|
|
elif self.language == 2: # 'nl' language
|
|
desc = "Tijd: " + desc_txt
|
|
return desc
|
|
|
|
def create_tags(self):
|
|
"""
|
|
Creates tags to objects (if provide)
|
|
"""
|
|
default_tag = (config.get('preferences.tag-on-import-format') if
|
|
config.get('preferences.tag-on-import') else None)
|
|
if default_tag:
|
|
tag_str = time.strftime(default_tag)
|
|
for tagobj in TAGOBJECTS:
|
|
tag_name = '%s %s' % (_(tagobj), tag_str)
|
|
tag = self.dbase.get_tag_from_name(tag_name)
|
|
if not tag:
|
|
tag = Tag()
|
|
tag.set_name(tag_name)
|
|
self.dbase.add_tag(tag, self.trans)
|
|
|
|
self.tagobject_list[tagobj] = tag
|
|
|
|
__rel_pat = re.compile(r'(r|w|)', re.VERBOSE)
|
|
def create_persons(self):
|
|
"""
|
|
Method to import Persons
|
|
"""
|
|
|
|
table = self.def_['Table_1']
|
|
LOG.info(table.get_field_names())
|
|
|
|
# We'll start with F02: Person last change
|
|
# Note: We like this to be computed just once.
|
|
person_ix = [0, 0]
|
|
for count in range(2, len(self.person_identifier)):
|
|
# We have seen some case insensitivity in DEF files ...
|
|
pid = self.person_identifier[count][self.language].lower()
|
|
pix = table.get_record_field_index(pid)
|
|
person_ix.append(pix)
|
|
|
|
# self.set_text(_('Importing individuals')) # non-functional for now
|
|
|
|
# Male / Female symbols
|
|
male_sym = self.def_.tables['Genealogical'].parms['male']
|
|
female_sym = self.def_.tables['Genealogical'].parms['female']
|
|
|
|
ind_id = self.opt_ind_id -1 # Option: Individuals IDs interator
|
|
for i, rec in enumerate(self.pers):
|
|
# Update at the begin due to approx. ton's of 'not recflds[1]'
|
|
self.update()
|
|
|
|
recflds = table.convert_record_to_list(rec, self.mems)
|
|
if not recflds[1]:
|
|
continue
|
|
|
|
ind_id += 1
|
|
# Option: Original Individuals IDs
|
|
if self.opt_ind_id < 0:
|
|
ind_id = i +1
|
|
|
|
# print(("Ind ID %d " % ind_id) + " ".join(("%s" % r) for r in rec))
|
|
person = self.__find_or_create_person(ind_id)
|
|
|
|
# process F03 Given Name, F07 Call Name
|
|
name = Name()
|
|
name.set_type(NameType.BIRTH)
|
|
|
|
first_name = recflds[person_ix[3]] # F03:
|
|
if first_name:
|
|
# replace if neccessary separators with ' '
|
|
first_name = re.sub(r'[,;]', ' ', first_name)
|
|
else:
|
|
# default first name 'Nomen nominandum'
|
|
first_name = 'N.N.'
|
|
name.set_first_name(first_name)
|
|
|
|
# process F04 Last Name
|
|
sur_prefix, sur_name = '', ''
|
|
if recflds[person_ix[4]]:
|
|
sur_prefix, sur_name = _split_surname(recflds[person_ix[4]]) # F04: INDI NAME
|
|
if not sur_name:
|
|
# default surname 'Nomen nominandum'
|
|
sur_name = 'N.N.'
|
|
surname = Surname()
|
|
surname.set_surname(sur_name)
|
|
if sur_prefix:
|
|
surname.set_prefix(sur_prefix)
|
|
name.add_surname(surname)
|
|
|
|
# process F06 Patronym
|
|
patronym = recflds[person_ix[6]] # F06: INDI _PATR
|
|
if patronym:
|
|
patronym_name = Surname()
|
|
patronym_name.set_surname(patronym)
|
|
patronym_name.set_origintype(NameOriginType.PATRONYMIC)
|
|
name.add_surname(patronym_name)
|
|
|
|
# process F10 - F12 Title(s)
|
|
title1 = recflds[person_ix[10]] # F10: INDI TITL
|
|
title2 = recflds[person_ix[11]] # F11: INDI _TITL2
|
|
title3 = recflds[person_ix[12]] # F12: INDI _TITL3
|
|
title = [_f for _f in [title1, title2, title3] if _f]
|
|
if title:
|
|
name.set_title(", ".join(title))
|
|
|
|
# General config: addtional individual citation
|
|
if self.default_source:
|
|
# Original individual ID from source
|
|
pageref = '[ID: I%06d] %s, %s' % (i +1, sur_name, first_name)
|
|
citation = self.__get_or_create_citation \
|
|
(self.source_title, recflds[person_ix[1]], # F01: Last Change
|
|
self.citation_page, pageref)
|
|
if citation and citation.handle:
|
|
person.add_citation(citation.handle)
|
|
|
|
# add tag to 'Person' object
|
|
self.__add_tag('Person', person)
|
|
|
|
# create diagnose message
|
|
diag_msg = "%s: %s %s" % (person.gramps_id,
|
|
first_name.encode('utf-8'),
|
|
sur_name.encode('utf-8'))
|
|
|
|
# prcesss F25 Birth Date
|
|
birth_date = self.__create_date_from_text \
|
|
(recflds[person_ix[25]], diag_msg) # F25: ... DATE
|
|
|
|
# process F07 Call Name
|
|
if recflds[person_ix[7]]:
|
|
# F07: INDI NAME NICK/INDI NAME ALIA/INDI CHR NICK
|
|
name.set_call_name(recflds[person_ix[7]])
|
|
else:
|
|
nick_name = first_name.split(' ')
|
|
if birth_date and len(nick_name) > 1: # Two or more firstname's
|
|
number = 0 # Firstname number
|
|
if birth_date.dateval[2] < 1900: # 1900: Common knowledge edge date
|
|
number = 1
|
|
name.set_call_name(nick_name[number])
|
|
|
|
# set the Person in database
|
|
person.set_primary_name(name)
|
|
|
|
# process F05 Gender
|
|
gender = recflds[person_ix[5]] # F05: INDI SEX
|
|
if gender == male_sym:
|
|
gender = Person.MALE
|
|
elif gender == female_sym:
|
|
gender = Person.FEMALE
|
|
else:
|
|
gender = Person.UNKNOWN
|
|
person.set_gender(gender)
|
|
|
|
# process F08 Alias
|
|
alias = recflds[person_ix[8]] # F08: INDI NAME _ALIA / INDI NAME COMM
|
|
if alias:
|
|
# expand separator with ' '
|
|
alias = re.sub(r'\.', '. ', alias)
|
|
alias_text = alias.split()
|
|
# two ways: Attribute-Nickname or AKA-Name
|
|
if len(alias_text) == 1:
|
|
attr = self.__create_attribute(alias, AttributeType.NICKNAME)
|
|
if attr:
|
|
person.add_attribute(attr)
|
|
else:
|
|
name = Name()
|
|
surname = Surname()
|
|
surname.set_surname(alias_text[-1].strip())
|
|
name.add_surname(surname)
|
|
name.set_first_name(' '.join(alias_text[0:-1]))
|
|
name.set_type(NameType.AKA)
|
|
person.add_alternate_name(name)
|
|
|
|
# process F09 Person Code
|
|
per_code = recflds[person_ix[9]] # F09: INDI REFN/INDI CODE
|
|
if per_code:
|
|
# We have seen some artefacts ...
|
|
per_cde = self.__rel_pat.match(per_code)
|
|
# Option: Relation code contains one/two letters
|
|
if not self.opt_relation_code and per_cde:
|
|
pass
|
|
else:
|
|
attr = self.__create_attribute(per_code, AttributeType.CUSTOM, "REFN")
|
|
if attr:
|
|
person.add_attribute(attr)
|
|
|
|
# process F15 Occupation
|
|
occupation = recflds[person_ix[15]] # F15: INDI OCCU
|
|
if occupation:
|
|
event, event_ref = self.__create_event_and_ref \
|
|
(EventType.OCCUPATION, occupation)
|
|
if event_ref:
|
|
person.add_event_ref(event_ref)
|
|
|
|
# process F16 Person Comment, F17 Person Note
|
|
comm = recflds[person_ix[16]] # F16: INDI _COMM / INDI COMM
|
|
note = recflds[person_ix[17]] # F17: INDI NOTE
|
|
note_text = [_f for _f in [comm, note] if _f]
|
|
note = self.__create_note(note_text, NoteType.PERSON)
|
|
if note and note.handle:
|
|
person.add_note(note.handle)
|
|
|
|
# process F18 - F24 Address Date, Place, Street, ZIP, Country, Phone, Info
|
|
# GEDCOM symbols: INDI RESI ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[person_ix[18]], diag_msg) # F18: ... DATE
|
|
street = recflds[person_ix[19]] # F19: ... ADDR
|
|
postal_code = recflds[person_ix[20]] # F20: ... ADDR POST/INDI RESI POST
|
|
place = self.__get_or_create_place \
|
|
(recflds[person_ix[21]]) # F21: ... ADDR CITY/INDI RESI PLAC
|
|
country = recflds[person_ix[22]] # F22: ... ADDR CTRY/INDI RESI CTRY
|
|
phone = recflds[person_ix[23]] # F23: ... PHON/INDI PHON
|
|
info = recflds[person_ix[24]] # F24: I... NOTE / INDI ADDR
|
|
|
|
address = None
|
|
if street or postal_code or country or phone:
|
|
# Create address
|
|
address = Address()
|
|
if date:
|
|
address.set_date_object(date)
|
|
if street:
|
|
address.set_street(street)
|
|
if recflds[person_ix[21]]:
|
|
address.set_city(recflds[person_ix[21]])
|
|
if postal_code:
|
|
address.set_postal_code(postal_code)
|
|
if country:
|
|
address.set_country(country)
|
|
if phone:
|
|
address.set_phone(phone)
|
|
|
|
# Option 1: add Notes to Address
|
|
note = self.__create_note(info, NoteType.ADDRESS)
|
|
if note and note.handle:
|
|
address.add_note(note.handle)
|
|
info = None
|
|
|
|
person.add_address(address)
|
|
|
|
if place:
|
|
desc = ''
|
|
if address and date:
|
|
if self.language == 0: # 'de' language
|
|
desc = 'siehe Adresse am '
|
|
elif self.language == 1: # 'en' language
|
|
desc = 'see Address on '
|
|
elif self.language == 2: # 'nl' language
|
|
desc = 'zie Adres op '
|
|
desc += displayer.display(date)
|
|
elif address:
|
|
if self.language == 0: # 'de' language
|
|
desc = 'siehe auch Adresse'
|
|
elif self.language == 1: # 'en' language
|
|
desc = 'see also Address'
|
|
elif self.language == 2: # 'nl' language
|
|
desc = 'zie ook Adres'
|
|
# Option 2: add Notes to Event
|
|
event, resi_ref = self.__create_event_and_ref \
|
|
(EventType.RESIDENCE, desc, date, place, '', info)
|
|
if resi_ref:
|
|
person.add_event_ref(resi_ref)
|
|
|
|
# process F25 - F31 Birth Date, Place, Time, Source, Reference, Text, Info
|
|
# GEDCOM symbols: INDI BIRT ...
|
|
# date = self.__create_date_from_text \ # Birth Date processed above
|
|
# (recflds[person_ix[25]], diag_msg) # F25: ... DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[person_ix[26]]) # F26: ... PLAC
|
|
birth_time = recflds[person_ix[27]] # F27: ... TIME
|
|
source = recflds[person_ix[28]] # F28: ... SOUR / ... SOUR TITL
|
|
source_refn = recflds[person_ix[29]] # F29: ... SOUR REFN
|
|
source_text = recflds[person_ix[30]] # F30: ... SOUR TEXT
|
|
info = recflds[person_ix[31]] # F31: INDI ... NOTE
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[person_ix[25]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if birth_date or place or info or citation:
|
|
desc = source_text
|
|
# Option: Birth time in description
|
|
if self.opt_birth_time:
|
|
time_text = self.__create_desc_from_text(birth_time)
|
|
desc += '; %s' % time_text
|
|
event, birth_ref = self.__create_event_and_ref \
|
|
(EventType.BIRTH, desc, birth_date, place, citation, info,
|
|
birth_time, AttributeType.TIME)
|
|
if birth_ref:
|
|
person.set_birth_ref(birth_ref)
|
|
|
|
# process F32 - F37 Baptism / Christening Date, Place, Religion,
|
|
# Source, Reference, Text, Info
|
|
# GEDCOM symbols: INDI CHR ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[person_ix[32]], diag_msg) # F32: ... DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[person_ix[33]]) # F33: ... PLAC
|
|
religion = recflds[person_ix[36]] # F34: ... RELI / INDI RELI
|
|
witness = recflds[person_ix[35]] # F35: ... _WITN / ... WITN
|
|
source = recflds[person_ix[36]] # F36: ... SOUR / ... SOUR TITL
|
|
source_refn = recflds[person_ix[37]] # F37: ... SOUR REFN
|
|
source_text = recflds[person_ix[38]] # F38: ... SOUR TEXT
|
|
info = recflds[person_ix[39]] # F39: ... NOTE
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[person_ix[32]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if date or place or info or citation:
|
|
event, chris_ref = self.__create_event_and_ref \
|
|
(EventType.CHRISTEN, source_text, date, place, citation, info,
|
|
witness, AttributeType.CUSTOM, _("Godfather"))
|
|
if chris_ref:
|
|
person.add_event_ref(chris_ref)
|
|
|
|
# process F34 Religion
|
|
if religion:
|
|
citation = None
|
|
if source != religion:
|
|
citation = self.__get_or_create_citation \
|
|
(religion, recflds[person_ix[32]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
event, reli_ref = self.__create_event_and_ref \
|
|
(EventType.RELIGION, '', date, '', citation)
|
|
if reli_ref:
|
|
person.add_event_ref(reli_ref)
|
|
|
|
# process F40 - F46 Death Date, Place, Time, Source, Reference, Text, Info
|
|
# GEDCOM symbols: INDI DEAT ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[person_ix[40]], diag_msg) # F40: ... DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[person_ix[41]]) # F41: ... PLAC
|
|
death_time = recflds[person_ix[42]] # F42: ... TIME
|
|
source = recflds[person_ix[43]] # F43: ... SOUR / ... SOUR TITL
|
|
source_refn = recflds[person_ix[44]] # F44: ... SOUR REFN
|
|
source_text = recflds[person_ix[45]] # F45: ... SOUR TEXT
|
|
info = recflds[person_ix[46]] # F46: ... NOTE
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[person_ix[40]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if date or place or info or citation:
|
|
desc = source_text
|
|
# Option: Death time in description
|
|
if self.opt_death_time:
|
|
time_text = self.__create_desc_from_text(death_time)
|
|
desc += '; %s' % time_text
|
|
if not self.opt_death_info2cause:
|
|
desc = info
|
|
event, death_ref = self.__create_event_and_ref \
|
|
(EventType.DEATH, desc, date, place, citation, None,
|
|
death_time, AttributeType.TIME)
|
|
if death_ref:
|
|
person.set_death_ref(death_ref)
|
|
|
|
# Option: Death info to Death cause
|
|
if source_text or (self.opt_death_info2cause and info):
|
|
desc = [_f for _f in [source_text, info] if _f]
|
|
desc = desc and '; '.join(desc) or None
|
|
if 'Todesursache:' in desc: # DE only
|
|
desc = desc[13:].strip()
|
|
|
|
event, event_ref = self.__create_event_and_ref \
|
|
(EventType.CAUSE_DEATH, desc)
|
|
if event_ref:
|
|
person.add_event_ref(event_ref)
|
|
|
|
# process F47 - F52 Cremation Date, Place, Source, Reference,
|
|
# Text, Info
|
|
# GEDCOM symbols: INDI CREM ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[person_ix[47]], diag_msg) # F47: ... DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[person_ix[48]]) # F48: ... PLAC
|
|
source = recflds[person_ix[49]] # F49: ... SOUR / ... SOUR TITL
|
|
source_refn = recflds[person_ix[50]] # F50: ... SOUR REFN
|
|
source_text = recflds[person_ix[51]] # F51: ... SOUR TEXT
|
|
info = recflds[person_ix[52]] # F52: ... INFO
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[person_ix[47]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if date or place or info or citation:
|
|
event, cremation_ref = self.__create_event_and_ref \
|
|
(EventType.CREMATION, source_text, date, place, citation, info)
|
|
if cremation_ref:
|
|
person.add_event_ref(cremation_ref)
|
|
|
|
# process F53 Burial Date, F54 Burial Place, F55 Burial Source,
|
|
# F56 Burial Reference, F57 Burial Text, F58 Burial Info
|
|
# GEDCOM symbols: INDI BURI ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[person_ix[53]], diag_msg) # F53: ... DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[person_ix[54]]) # F54: ... PLAC
|
|
source = recflds[person_ix[55]] # F49: ... SOUR / ... SOUR TITL
|
|
source_refn = recflds[person_ix[56]] # F50: ... SOUR REFN
|
|
source_text = recflds[person_ix[57]] # F51: ... SOUR TEXT
|
|
info = recflds[person_ix[58]] # F58: ... INFO
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[person_ix[53]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if date or place or info or citation:
|
|
event, buri_ref = self.__create_event_and_ref \
|
|
(EventType.BURIAL, source_text, date, place, citation, info)
|
|
if buri_ref:
|
|
person.add_event_ref(buri_ref)
|
|
|
|
# commit the Person
|
|
self.dbase.commit_person(person, self.trans)
|
|
|
|
def create_families(self):
|
|
"""
|
|
Method to import Families
|
|
"""
|
|
|
|
table = self.def_['Table_2']
|
|
LOG.info(table.get_field_names())
|
|
|
|
# We'll start with F03: Husband
|
|
# Note: We like this to be computed just once.
|
|
family_ix = [0, 0, 0]
|
|
for count in range(3, len(self.family_identifier)):
|
|
# We've seen some case insensitivity in DEF files ...
|
|
fid = self.family_identifier[count][self.language].lower()
|
|
fix = table.get_record_field_index(fid)
|
|
family_ix.append(fix)
|
|
|
|
# The records are numbered 1..N
|
|
# self.set_text(_('Importing families')) # non-functional for now
|
|
fam_id = self.opt_fam_id -1 # Option: Family IDs interator
|
|
for i, rec in enumerate(self.rels):
|
|
# Update at the begin
|
|
self.update()
|
|
|
|
husband = rec[family_ix[3]] # F03: FAM HUSB
|
|
wife = rec[family_ix[4]] # F04: FAM WIFE
|
|
|
|
if husband > 0 or wife > 0:
|
|
fam_id += 1
|
|
# Option: Original family IDs
|
|
if self.opt_fam_id < 0:
|
|
fam_id = i +1
|
|
# print(("Family ID %d " % fam_id) + " ".join(("%s" % r) for r in rec))
|
|
|
|
recflds = table.convert_record_to_list(rec, self.mems)
|
|
self.high_fam_id = fam_id
|
|
family = self.__find_or_create_family(fam_id)
|
|
|
|
# process F03 / F04 Husband / Wife
|
|
husband_handle = None
|
|
if husband > 0:
|
|
husband_handle = self.__find_person_handle(husband)
|
|
family.set_father_handle(husband_handle)
|
|
husband_person = self.dbase.get_person_from_handle(husband_handle)
|
|
husband_person.add_family_handle(family.get_handle())
|
|
self.dbase.commit_person(husband_person, self.trans)
|
|
wife_handle = None
|
|
if wife > 0:
|
|
wife_handle = self.__find_person_handle(wife)
|
|
family.set_mother_handle(wife_handle)
|
|
wife_person = self.dbase.get_person_from_handle(wife_handle)
|
|
wife_person.add_family_handle(family.get_handle())
|
|
self.dbase.commit_person(wife_person, self.trans)
|
|
|
|
self.fm2fam[husband_handle, wife_handle] = family
|
|
diag_msg = "%s: %s %s" % \
|
|
(family.gramps_id,
|
|
husband_person.gramps_id if husband_handle else "",
|
|
wife_person.gramps_id if wife_handle else "")
|
|
|
|
# Option: Addtional family citation
|
|
if self.default_source:
|
|
husband_name = husband_person.get_primary_name().get_surname()
|
|
wife_name = wife_person.get_primary_name().get_surname()
|
|
# Original family ID from source
|
|
pageref = '[ID: F%05d] %s -- %s' % (i +1, husband_name, wife_name)
|
|
citation = self.__get_or_create_citation \
|
|
(self.source_title, recflds[family_ix[1]], # F01: Last Time Change
|
|
self.citation_page, pageref)
|
|
if citation and citation.handle:
|
|
family.add_citation(citation.handle)
|
|
|
|
# add tag to 'Family' object
|
|
self.__add_tag('Family', family)
|
|
|
|
# process F08 - F13 Civil Union Date, Place, Source,
|
|
# Reference, Text, Info
|
|
# GEDCOM symbols: FAM _LIV ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[family_ix[8]], diag_msg) # F08: ... DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[family_ix[9]]) # F09: ... PLAC
|
|
source = recflds[family_ix[10]] # F10: ... SOUR/FAM _LIV SOUR TITL
|
|
source_refn = recflds[family_ix[11]] # F11: ... SOUR REFN
|
|
source_text = recflds[family_ix[12]] # F12: ... SOUR TEXT
|
|
info = recflds[family_ix[13]] # F13: ... NOTE
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[family_ix[8]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if date or place or info or citation:
|
|
if self.language == 0: # 'de' language
|
|
evt_type = 'Lebensgemeinschaft'
|
|
elif self.language == 1: # 'en' language
|
|
evt_type = 'Civil union'
|
|
elif self.language == 2: # 'nl' language
|
|
evt_type = 'Samenwonen'
|
|
event, civu_ref = self.__create_event_and_ref \
|
|
(EventType.UNKNOWN, source_text, date, place, citation, info)
|
|
event.set_type((EventType.CUSTOM, evt_type))
|
|
if civu_ref:
|
|
family.add_event_ref(civu_ref)
|
|
|
|
# Type of relation
|
|
family.set_relationship(FamilyRelType(FamilyRelType.CIVIL_UNION))
|
|
|
|
# process F14 - F20 Marriage License Date, Place, Witness,
|
|
# Source, Reference, Text, Info
|
|
# GEDCOM symbols: FAM MARB ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[family_ix[14]], diag_msg) # F14: ... DATE/FAM REGS DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[family_ix[15]]) # F15: ... PLAC/FAM REGS PLAC
|
|
witness = recflds[family_ix[16]] # F16: ... _WITN/FAM MARB WITN
|
|
source = recflds[family_ix[17]] # F17: ... SOUR/FAM MARB SOUR TITL/FAM REGS SOUR
|
|
source_refn = recflds[family_ix[18]] # F18: ... SOUR REFN/FAM REGS SOUR REFN
|
|
source_text = recflds[family_ix[19]] # F19: ... SOUR TEXT
|
|
info = recflds[family_ix[20]] # F20: ... NOTE
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[family_ix[14]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if date or place or info or citation:
|
|
desc = source_text
|
|
desc = [_f for _f in [source_text, info] if _f]
|
|
desc = desc and '; '.join(desc) or None
|
|
event, marl_ref = self.__create_event_and_ref \
|
|
(EventType.MARR_BANNS, desc, date, place, citation, '',
|
|
witness, AttributeType.WITNESS)
|
|
if marl_ref:
|
|
family.add_event_ref(marl_ref)
|
|
|
|
# process F21 - F27 Civil Marriage Date, Place, Witness,
|
|
# Source, Reference, Text, Info
|
|
# GEDCOM symbols: FAM MARR(Civil) ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[family_ix[21]], diag_msg) # F21: ... DATE/FAM MARR DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[family_ix[22]]) # F22: ... PLAC/FAM MARR PLAC
|
|
witness = recflds[family_ix[23]] # F23: ... _WITN/FAM MARR _WITN/FAM MARR WITN/FAM WITN
|
|
source = recflds[family_ix[24]] # F24: ... SOUR/FAM MARR SOUR/FAM MARR SOUR TITL
|
|
source_refn = recflds[family_ix[25]] # F25: ... SOUR REFN/FAM MARR SOUR REFN
|
|
source_text = recflds[family_ix[26]] # F26: ... SOUR TEXT/FAM MARR SOUR TEXT
|
|
info = recflds[family_ix[27]] # F27: ... NOTE
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[family_ix[21]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if date or place or info or citation:
|
|
desc = source_text
|
|
if not desc:
|
|
# 'Civil' is widely accepted and language independent
|
|
desc = "Civil"
|
|
event, mar_ref = self.__create_event_and_ref \
|
|
(EventType.MARRIAGE, desc, date, place, citation, info,
|
|
witness, AttributeType.WITNESS)
|
|
if mar_ref:
|
|
family.add_event_ref(mar_ref)
|
|
|
|
# Type of relation
|
|
family.set_relationship(FamilyRelType(FamilyRelType.MARRIED))
|
|
|
|
# process F28 - F35 Church Wedding Date, Place, Church, Witness,
|
|
# Source, Reference, Text, Info
|
|
# GEDCOM symbols: FAM MARR(Church) ...
|
|
wedding_date = self.__create_date_from_text \
|
|
(recflds[family_ix[28]], diag_msg) # F28: ... DATE / FAM ORDI DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[family_ix[29]]) # F29: ... DATE / FAM ORDI PLACE
|
|
church = recflds[family_ix[30]] # F30: ... _CHUR / FAM ORDI _CHUR / FAM ORDI RELI
|
|
witness = recflds[family_ix[31]] # F31: ... _WITN / FAM ORDI _WITN / FAM ORDI WITN
|
|
source = recflds[family_ix[32]] # F32: ... SOUR / FAM ORDI SOUR / FAM ORDI SOUR TITL
|
|
source_refn = recflds[family_ix[33]] # F33: ... SOUR REFN / FAM ORDI SOUR REFN
|
|
source_text = recflds[family_ix[34]] # F34: ... SOUR TEXT / FAM ORDI SOUR TEXT
|
|
info = recflds[family_ix[35]] # F35 ... INFO
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[family_ix[28]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if wedding_date or place or info or citation:
|
|
desc = [_f for _f in [church, source_text] if _f]
|
|
desc = desc and '; '.join(desc) or None
|
|
if not desc:
|
|
if self.language == 0: # 'de' language
|
|
desc = 'Trauung'
|
|
elif self.language == 1: # 'en' language
|
|
desc = 'Wedding'
|
|
elif self.language == 2: # 'nl' language
|
|
desc = 'Kerkelijk huwelijk'
|
|
event, marc_ref = self.__create_event_and_ref \
|
|
(EventType.MARRIAGE, desc, wedding_date, place, citation, info,
|
|
witness, AttributeType.WITNESS)
|
|
if marc_ref:
|
|
family.add_event_ref(marc_ref)
|
|
|
|
# Type of relation
|
|
family.set_relationship(FamilyRelType(FamilyRelType.MARRIED))
|
|
|
|
# process F05 - F07 Relation Code, Note, Info
|
|
rel_code = recflds[family_ix[5]] # F05: FAM REFN / FAM CODE
|
|
if rel_code:
|
|
# We have seen some artefacts ...
|
|
rel_cde = self.__rel_pat.match(rel_code)
|
|
# Option: Relation code contains one/two letters
|
|
if not self.opt_relation_code and rel_cde:
|
|
pass
|
|
else:
|
|
attr = self.__create_attribute(rel_code, AttributeType.CUSTOM, "REFN")
|
|
if attr:
|
|
family.add_attribute(attr)
|
|
|
|
comm = recflds[family_ix[6]] # F06: FAM _COMM/FAM COMM
|
|
note = recflds[family_ix[7]] # F07: FAM NOTE
|
|
note_text = [_f for _f in [comm, note] if _f]
|
|
if note_text:
|
|
cnt = None
|
|
if len(note_text) > 0:
|
|
note_cont = (' '.join(note_text)).split(' ')
|
|
else:
|
|
note_cont = note_text.split(' ')
|
|
if note_cont[0] == 'Wohnort:': # only DE
|
|
cnt = 1
|
|
elif note_cont[0] == 'zukünftiger' and \
|
|
note_cont[1] == 'Wohnort:': # only DE
|
|
cnt = 2
|
|
else:
|
|
note = self.__create_note(note_text, NoteType.FAMILY)
|
|
if note and note.handle:
|
|
family.add_note(note.handle)
|
|
|
|
if cnt:
|
|
if wedding_date:
|
|
date_text = _('after') + ' ' + str(wedding_date.dateval[2]) # Wedding Year
|
|
date = self.__create_date_from_text \
|
|
(date_text, diag_msg) # F28: ... DATE / FAM ORDI DATE
|
|
place_text = ''
|
|
for i in range(cnt, len(note_cont)): # Add all elements of Note Content
|
|
place_text += note_cont[i] + ' '
|
|
place_text = place_text.rstrip() # Strip whitespace
|
|
place = self.__get_or_create_place(place_text)
|
|
event, place_ref = self.__create_event_and_ref \
|
|
(EventType.RESIDENCE, None, date, place, citation)
|
|
if place_ref:
|
|
family.add_event_ref(place_ref)
|
|
|
|
# process F36 - F41 Divorce Date, Place, Source, Text, Reference, Info
|
|
# GEDCOM symbols: FAM DIV ...
|
|
date = self.__create_date_from_text \
|
|
(recflds[family_ix[36]], diag_msg) # F36: ... DATE / FAM DIVO DATE
|
|
place = self.__get_or_create_place \
|
|
(recflds[family_ix[37]]) # F37: ... PLAC / FAM DIVO PlAC
|
|
source = recflds[family_ix[38]] # F38: ... SOUR / FAM DIV SOUR TITL
|
|
source_refn = recflds[family_ix[39]] # F39: ... SOUR REFN
|
|
source_text = recflds[family_ix[40]] # F40: ... SOUR TEXT
|
|
info = recflds[family_ix[41]] # F41: ... INFO
|
|
citation = self.__get_or_create_citation \
|
|
(source, recflds[family_ix[36]], source_refn, '', 3,
|
|
'', self.citation_attr)
|
|
|
|
if date or place or info or citation:
|
|
desc = source_text
|
|
event, div_ref = self.__create_event_and_ref \
|
|
(EventType.DIVORCE, desc, date, place, citation, info)
|
|
if div_ref:
|
|
family.add_event_ref(div_ref)
|
|
|
|
# commit the Family
|
|
self.dbase.commit_family(family, self.trans)
|
|
|
|
def add_children(self):
|
|
"""
|
|
Method to add Children.
|
|
"""
|
|
|
|
# Once more to record the father and mother
|
|
table = self.def_['Table_1']
|
|
|
|
# We have seen some case insensitivity in DEF files ...
|
|
person_F13 = table.get_record_field_index \
|
|
(self.person_identifier[13][self.language].lower()) # F13: Father
|
|
person_F14 = table.get_record_field_index \
|
|
(self.person_identifier[14][self.language].lower()) # F14: Mother
|
|
|
|
# The records are numbered 1..N
|
|
# self.set_text(_('Adding children')) # non-functional for now
|
|
for i, rec in enumerate(self.pers):
|
|
# Update at the begin
|
|
self.update()
|
|
|
|
ind_id = i +1
|
|
# print(("Person ID %d " % ind_id) + " ".join(("%s" % r) for r in rec))
|
|
|
|
father = rec[person_F13] # F13: Father
|
|
mother = rec[person_F14] # F14: Mother
|
|
if father > 0 or mother > 0:
|
|
# Find the family with this Father and Mother
|
|
person_handle = self.__find_person_handle(ind_id)
|
|
father_handle = father > 0 and self.__find_person_handle(father) or None
|
|
mother_handle = mother > 0 and self.__find_person_handle(mother) or None
|
|
if father > 0 and not father_handle:
|
|
LOG.warning(_("Cannot find father for I%(person)s (Father=%(id)d)"), \
|
|
{'person' : ind_id, 'father' : father})
|
|
elif mother > 0 and not mother_handle:
|
|
LOG.warning(_("Cannot find mother for I%(person)s (Mother=%(mother)d)"), \
|
|
{'person' : ind_id, 'mother' : mother})
|
|
else:
|
|
family = self.fm2fam.get((father_handle, mother_handle), None)
|
|
if not family:
|
|
# Family not present in REL. Create a new one.
|
|
self.high_fam_id += 1
|
|
fam_id = self.high_fam_id
|
|
family = self.__find_or_create_family(fam_id)
|
|
|
|
if father_handle:
|
|
family.set_father_handle(father_handle)
|
|
father_person = self.dbase.get_person_from_handle \
|
|
(father_handle)
|
|
father_person.add_family_handle(family.get_handle())
|
|
# commit the Father
|
|
self.dbase.commit_person(father_person, self.trans)
|
|
|
|
if mother_handle:
|
|
family.set_mother_handle(mother_handle)
|
|
mother_person = self.dbase.get_person_from_handle \
|
|
(mother_handle)
|
|
mother_person.add_family_handle(family.get_handle())
|
|
# commit the Mother
|
|
self.dbase.commit_person(mother_person, self.trans)
|
|
|
|
if family:
|
|
childref = ChildRef()
|
|
childref.set_reference_handle(person_handle)
|
|
if childref:
|
|
family.add_child_ref(childref)
|
|
# commit the Family
|
|
self.dbase.commit_family(family, self.trans)
|
|
|
|
person = self.dbase.get_person_from_handle(person_handle)
|
|
if person:
|
|
person.add_parent_family_handle(family.get_handle())
|
|
# commit the Child
|
|
self.dbase.commit_person(person, self.trans)
|