gramps/src/plugins/import/ImportCsv.py
2011-12-04 17:09:17 +00:00

833 lines
36 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 Douglas S. Blank
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2008 Raphael Ackerman
# Copyright (C) 2008 Brian G. Matherly
# Copyright (C) 2011 Tim G L Lyons
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"Import from CSV Spreadsheet"
#-------------------------------------------------------------------------
#
# Standard Python Modules
#
#-------------------------------------------------------------------------
import time
import csv
import codecs
#------------------------------------------------------------------------
#
# Set up logging
#
#------------------------------------------------------------------------
import logging
LOG = logging.getLogger(".ImportCSV")
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from gen.ggettext import sgettext as _
from gen.ggettext import ngettext
import gen.lib
from gen.db import DbTxn
from gen.plug.utils import OpenFileOrStdin
from QuestionDialog import ErrorDialog
from DateHandler import parser as _dp
from Utils import gender as gender_map
from Utils import create_id
from gui.utils import ProgressMeter
from gen.lib.eventroletype import EventRoleType
#-------------------------------------------------------------------------
#
# Support Functions
#
#-------------------------------------------------------------------------
def get_primary_event_ref_from_type(dbase, person, event_name):
"""
>>> get_primary_event_ref_from_type(dbase, Person(), "Baptism"):
"""
for ref in person.event_ref_list:
if ref.get_role() == EventRoleType.PRIMARY:
event = dbase.get_event_from_handle(ref.ref)
if event and event.type.is_type(event_name):
return ref
return None
#-------------------------------------------------------------------------
#
# Encoding support for CSV, from http://docs.python.org/lib/csv-examples.html
#
#-------------------------------------------------------------------------
class UTF8Recoder(object):
"""
Iterator that reads an encoded stream and reencodes the input to UTF-8
"""
def __init__(self, stream, encoding):
self.reader = codecs.getreader(encoding)(stream)
def __iter__(self):
return self
def next(self):
"Encode the next line of the file."
return self.reader.next().encode("utf-8")
class UnicodeReader(object):
"""
A CSV reader which will iterate over lines in the CSV file,
which is encoded in the given encoding.
"""
def __init__(self, csvfile, encoding="utf-8", **kwds):
self.first_row = True
csvfile = UTF8Recoder(csvfile, encoding)
self.reader = csv.reader(csvfile, **kwds)
def next(self):
"Read the next line of the file."
row = self.reader.next()
rowlist = [unicode(s, "utf-8") for s in row]
# Add check for Byte Order Mark (Windows, Notepad probably):
if self.first_row:
if len(rowlist) > 0 and rowlist[0].startswith(u"\ufeff"):
rowlist[0] = rowlist[0][1:]
self.first_row = False
return rowlist
def __iter__(self):
return self
#-------------------------------------------------------------------------
#
# Support and main functions
#
#-------------------------------------------------------------------------
def rd(line_number, row, col, key, default = None):
""" Return Row data by column name """
if key in col:
if col[key] >= len(row):
LOG.warn("missing '%s, on line %d" % (key, line_number))
return default
retval = row[col[key]].strip()
if retval == "":
return default
else:
return retval
else:
return default
def importData(dbase, filename, callback=None):
"""Function called by Gramps to import data on persons in CSV format."""
parser = CSVParser(dbase, callback)
try:
with OpenFileOrStdin(filename, 'b') as filehandle:
parser.parse(filehandle)
except EnvironmentError, err:
ErrorDialog(_("%s could not be opened\n") % filename, str(err))
return
return None # This module doesn't provide info about what got imported.
#-------------------------------------------------------------------------
#
# CSV Parser
#
#-------------------------------------------------------------------------
class CSVParser(object):
"""Class to read data in CSV format from a file object."""
def __init__(self, dbase, callback):
self.db = dbase
self.callback = callback
self.trans = None
self.lineno = 0
self.index = 0
self.fam_count = 0
self.indi_count = 0
self.pref = {} # person ref, internal to this sheet
self.fref = {} # family ref, internal to this sheet
column2label = {
"surname": ("Lastname", "Surname", _("Surname"), "lastname",
"last_name", "surname", _("surname")),
"firstname": ("Firstname", "Given name", _("Given name"), "Given",
_("Given"), "firstname", "first_name", "given_name",
"given name", _("given name"), "given", _("given")),
"callname": ("Callname", "Call name", _("Call name"), "Call",
_("Call"), "callname", "call_name", "call name", "call",
_("call")),
"title": ("Title", _("Person|Title"), "title", _("Person|title")),
"prefix": ("Prefix", _("Prefix"), "prefix", _("prefix")),
"suffix": ("Suffix", _("Suffix"), "suffix", _("suffix")),
"gender": ("Gender", _("Gender"), "gender", _("gender")),
"source": ("Source", _("Source"), "source", _("source")),
"note": ("Note", _("Note"), "note", _("note")),
"birthplace": ("Birthplace", "Birth place", _("Birth place"),
"birthplace", "birth_place", "birth place", _("birth place")),
"birthdate": ("Birthdate", "Birth date", _("Birth date"),
"birthdate", "birth_date", "birth date", _("birth date")),
"birthsource": ("Birthsource", "Birth source", _("Birth source"),
"birthsource", "birth_source", "birth source",
_("birth source")),
"baptismplace": ("Baptismplace", "Baptism place",
_("Baptism place"), "baptismplace", "baptism place",
_("baptism place")),
"baptismdate": ("Baptismdate", "Baptism date", _("Baptism date"),
"baptismdate", "baptism date", _("baptism date")),
"baptismsource": ("Baptismsource", "Baptism source",
_("Baptism source"), "baptismsource", "baptism source",
_("baptism source")),
"burialplace": ("Burialplace", "Burial place", _("Burial place"),
"burialplace", "burial place", _("burial place")),
"burialdate": ("Burialdate", "Burial date", _("Burial date"),
"burialdate", "burial date", _("burial date")),
"burialsource": ("Burialsource", "Burial source",
_("Burial source"), "burialsource", "burial source",
_("burial source")),
"deathplace": ("Deathplace", "Death place", _("Death place"),
"deathplace", "death_place", "death place", _("death place")),
"deathdate": ("Deathdate", "Death date", _("Death date"),
"deathdate", "death_date", "death date", _("death date")),
"deathsource": ("Deathsource", "Death source", _("Death source"),
"deathsource", "death_source", "death source",
_("death source")),
"deathcause": ("Deathcause", "Death cause", _("Death cause"),
"deathcause", "death_cause", "death cause", _("death cause")),
"grampsid": ("Grampsid", "ID", "Gramps id", _("Gramps ID"),
"grampsid", "id", "gramps_id", "gramps id", _("Gramps id")),
"person": ("Person", _("Person"), "person", _("person")),
# ----------------------------------
"child": ("Child", _("Child"), "child", _("child")),
"family": ("Family", _("Family"), "family", _("family")),
# ----------------------------------
"wife": ("Mother", _("Mother"), "Wife", _("Wife"), "Parent2",
_("Parent2"), "mother", _("mother"), "wife", _("wife"),
"parent2", _("parent2")),
"husband": ("Father", _("Father"), "Husband", _("Husband"),
"Parent1", _("Parent1"), "father", _("father"), "husband",
_("husband"), "parent1", _("parent1")),
"marriage": ("Marriage", _("Marriage"), "marriage", _("marriage")),
"date": ("Date", _("Date"), "date", _("date")),
"place": ("Place", _("Place"), "place", _("place")),
}
lab2col_dict = []
for key in column2label.keys():
for val in column2label[key]:
lab2col_dict.append((val, key))
self.label2column = dict(lab2col_dict)
def cleanup_column_name(self, column):
"""Handle column aliases for CSV spreadsheet import and SQL."""
return self.label2column.get(column, column)
def read_csv(self, filehandle):
"Read the data from the file and return it as a list."
reader = UnicodeReader(filehandle)
try:
data = [[r.strip() for r in row] for row in reader]
except csv.Error, err:
ErrorDialog(_('format error: line %(line)d: %(zero)s') % {
'line' : reader.reader.line_num, 'zero' : err } )
return None
return data
def lookup(self, type_, id_):
"""
Return the object of type type_ with id id_ from db or previously
stored value.
"""
if id_ is None:
return None
if type_ == "family":
if id_.startswith("[") and id_.endswith("]"):
id_ = self.db.fid2user_format(id_[1:-1])
db_lookup = self.db.get_family_from_gramps_id(id_)
if db_lookup is None:
return self.lookup(type_, id_)
else:
return db_lookup
elif id_.lower() in self.fref:
return self.fref[id_.lower()]
else:
return None
elif type_ == "person":
if id_.startswith("[") and id_.endswith("]"):
id_ = self.db.id2user_format(id_[1:-1])
db_lookup = self.db.get_person_from_gramps_id(id_)
if db_lookup is None:
return self.lookup(type_, id_)
else:
return db_lookup
elif id_.lower() in self.pref:
return self.pref[id_.lower()]
else:
return None
else:
LOG.warn("invalid lookup type in CSV import: '%s'" % type_)
return None
def storeup(self, type_, id_, object_):
"Store object object_ of type type_ in a dictionary under key id_."
if id_.startswith("[") and id_.endswith("]"):
id_ = id_[1:-1]
#return # do not store gramps people; go look them up
if type_ == "person":
id_ = self.db.id2user_format(id_)
self.pref[id_.lower()] = object_
elif type_ == "family":
id_ = self.db.fid2user_format(id_)
self.fref[id_.lower()] = object_
else:
LOG.warn("invalid storeup type in CSV import: '%s'" % type_)
def parse(self, filehandle):
"""
Prepare the database and parse the input file.
:param filehandle: open file handle positioned at start of the file
"""
data = self.read_csv(filehandle)
progress = ProgressMeter(_('CSV Import'))
progress.set_pass(_('Reading data...'), 1)
progress.set_pass(_('Importing data...'), len(data))
tym = time.time()
self.db.disable_signals()
with DbTxn(_("CSV import"), self.db, batch=True) as self.trans:
self._parse_csv_data(data, progress)
self.db.enable_signals()
self.db.request_rebuild()
tym = time.time() - tym
msg = ngettext('Import Complete: %d second',
'Import Complete: %d seconds', tym ) % tym
LOG.debug(msg)
LOG.debug("New Families: %d" % self.fam_count)
LOG.debug("New Individuals: %d" % self.indi_count)
progress.close()
def _parse_csv_data(self, data, progress=None):
"""Parse each line of the input data and act accordingly."""
self.lineno = 0
self.index = 0
self.fam_count = 0
self.indi_count = 0
self.pref = {} # person ref, internal to this sheet
self.fref = {} # family ref, internal to this sheet
header = None
line_number = 0
for row in data:
if progress is not None:
progress.step()
line_number += 1
if "".join(row) == "": # no blanks are allowed inside a table
header = None # clear headers, ready for next "table"
continue
######################################
if header is None:
header = [self.cleanup_column_name(r) for r in row]
col = {}
count = 0
for key in header:
col[key] = count
count += 1
continue
# three different kinds of data: person, family, and marriage
if (("marriage" in header) or
("husband" in header) or
("wife" in header)):
self._parse_marriage(line_number, row, col)
elif "family" in header:
self._parse_family(line_number, row, col)
elif "surname" in header:
self._parse_person(line_number, row, col)
else:
LOG.warn("ignoring line %d" % line_number)
return None
def _parse_marriage(self, line_number, row, col):
"Parse the content of a Marriage,Husband,Wife line."
marriage_ref = rd(line_number, row, col, "marriage")
husband = rd(line_number, row, col, "husband")
wife = rd(line_number, row, col, "wife")
marriagedate = rd(line_number, row, col, "date")
marriageplace = rd(line_number, row, col, "place")
marriagesource = rd(line_number, row, col, "source")
note = rd(line_number, row, col, "note")
wife = self.lookup("person", wife)
husband = self.lookup("person", husband)
if husband is None and wife is None:
# might have children, so go ahead and add
LOG.warn("no parents on line %d; adding family anyway" %
line_number)
family = self.get_or_create_family(marriage_ref, husband, wife)
# adjust gender, if not already provided
if husband:
# this is just a guess, if unknown
if husband.get_gender() == gen.lib.Person.UNKNOWN:
husband.set_gender(gen.lib.Person.MALE)
self.db.commit_person(husband, self.trans)
if wife:
# this is just a guess, if unknown
if wife.get_gender() == gen.lib.Person.UNKNOWN:
wife.set_gender(gen.lib.Person.FEMALE)
self.db.commit_person(wife, self.trans)
if marriage_ref:
self.storeup("family", marriage_ref.lower(), family)
if marriagesource:
# add, if new
new, marriagesource = self.get_or_create_source(marriagesource)
if marriageplace:
# add, if new
new, marriageplace = self.get_or_create_place(marriageplace)
if marriagedate:
marriagedate = _dp.parse(marriagedate)
if marriagedate or marriageplace or marriagesource or note:
# add, if new; replace, if different
new, marriage = self.get_or_create_event(family,
gen.lib.EventType.MARRIAGE, marriagedate,
marriageplace, marriagesource)
if new:
mar_ref = gen.lib.EventRef()
mar_ref.set_reference_handle(marriage.get_handle())
family.add_event_ref(mar_ref)
self.db.commit_family(family, self.trans)
# only add note to event:
# append notes, if previous notes
if note:
previous_notes_list = marriage.get_note_list()
updated_note = False
for note_handle in previous_notes_list:
previous_note = self.db.get_note_from_handle(
note_handle)
if previous_note.type == gen.lib.NoteType.EVENT:
previous_text = previous_note.get()
if note not in previous_text:
note = previous_text + "\n" + note
previous_note.set(note)
self.db.commit_note(previous_note, self.trans)
updated_note = True
break
if not updated_note:
# add new note here
new_note = gen.lib.Note()
new_note.handle = create_id()
new_note.type.set(gen.lib.NoteType.EVENT)
new_note.set(note)
self.db.add_note(new_note, self.trans)
marriage.add_note(new_note.handle)
self.db.commit_event(marriage, self.trans)
def _parse_family(self, line_number, row, col):
"Parse the content of a family line"
family_ref = rd(line_number, row, col, "family")
if family_ref is None:
LOG.warn("no family reference found for family on line %d" %
line_number)
return # required
child = rd(line_number, row, col, "child")
source = rd(line_number, row, col, "source")
note = rd(line_number, row, col, "note")
gender = rd(line_number, row, col, "gender")
child = self.lookup("person", child)
family = self.lookup("family", family_ref)
if family is None:
LOG.warn("no matching family reference found for family "
"on line %d" % line_number)
return
if child is None:
LOG.warn("no matching child reference found for family "
"on line %d" % line_number)
return
# is this child already in this family? If so, don't add
LOG.debug("children: %s", [ref.ref for ref in
family.get_child_ref_list()])
LOG.debug("looking for: %s", child.get_handle())
if child.get_handle() not in [ref.ref for ref in
family.get_child_ref_list()]:
# add child to family
LOG.debug(" adding child [%s] to family [%s]",
child.get_gramps_id(), family.get_gramps_id())
childref = gen.lib.ChildRef()
childref.set_reference_handle(child.get_handle())
family.add_child_ref( childref)
self.db.commit_family(family, self.trans)
child.add_parent_family_handle(family.get_handle())
if gender:
# replace
gender = gender.lower()
if gender == gender_map[gen.lib.Person.MALE].lower():
gender = gen.lib.Person.MALE
elif gender == gender_map[gen.lib.Person.FEMALE].lower():
gender = gen.lib.Person.FEMALE
else:
gender = gen.lib.Person.UNKNOWN
child.set_gender(gender)
if source:
# add, if new
dummy_new, source = self.get_or_create_source(source)
self.find_and_set_citation(child, source)
# put note on child
if note:
# append notes, if previous notes
previous_notes_list = child.get_note_list()
updated_note = False
for note_handle in previous_notes_list:
previous_note = self.db.get_note_from_handle(note_handle)
if previous_note.type == gen.lib.NoteType.PERSON:
previous_text = previous_note.get()
if note not in previous_text:
note = previous_text + "\n" + note
previous_note.set(note)
self.db.commit_note(previous_note, self.trans)
updated_note = True
break
if not updated_note:
# add new note here
new_note = gen.lib.Note()
new_note.handle = create_id()
new_note.type.set(gen.lib.NoteType.PERSON)
new_note.set(note)
self.db.add_note(new_note, self.trans)
child.add_note(new_note.handle)
self.db.commit_person(child, self.trans)
def _parse_person(self, line_number, row, col):
"Parse the content of a Person line."
surname = rd(line_number, row, col, "surname")
firstname = rd(line_number, row, col, "firstname", "")
callname = rd(line_number, row, col, "callname")
title = rd(line_number, row, col, "title")
prefix = rd(line_number, row, col, "prefix")
suffix = rd(line_number, row, col, "suffix")
gender = rd(line_number, row, col, "gender")
source = rd(line_number, row, col, "source")
note = rd(line_number, row, col, "note")
birthplace = rd(line_number, row, col, "birthplace")
birthdate = rd(line_number, row, col, "birthdate")
birthsource = rd(line_number, row, col, "birthsource")
baptismplace = rd(line_number, row, col, "baptismplace")
baptismdate = rd(line_number, row, col, "baptismdate")
baptismsource = rd(line_number, row, col, "baptismsource")
burialplace = rd(line_number, row, col, "burialplace")
burialdate = rd(line_number, row, col, "burialdate")
burialsource = rd(line_number, row, col, "burialsource")
deathplace = rd(line_number, row, col, "deathplace")
deathdate = rd(line_number, row, col, "deathdate")
deathsource = rd(line_number, row, col, "deathsource")
deathcause = rd(line_number, row, col, "deathcause")
grampsid = rd(line_number, row, col, "grampsid")
person_ref = rd(line_number, row, col, "person")
#########################################################
# if this person already exists, don't create them
person = self.lookup("person", person_ref)
if person is None:
if surname is None:
LOG.warn("empty surname for new person on line %d" %
line_number)
surname = ""
# new person
person = self.create_person()
name = gen.lib.Name()
name.set_type(gen.lib.NameType(gen.lib.NameType.BIRTH))
name.set_first_name(firstname)
surname_obj = gen.lib.Surname()
surname_obj.set_surname(surname)
name.add_surname(surname_obj)
person.set_primary_name(name)
else:
name = person.get_primary_name()
#########################################################
if person_ref is not None:
self.storeup("person", person_ref, person)
# replace
if surname is not None:
name.get_primary_surname().set_surname(surname)
if firstname is not None:
name.set_first_name(firstname)
if callname is not None:
name.set_call_name(callname)
if title is not None:
name.set_title(title)
if prefix is not None:
name.get_primary_surname().set_prefix(prefix)
name.group_as = '' # HELP? what should I do here?
if suffix is not None:
name.set_suffix(suffix)
if note is not None:
# append notes, if previous notes
previous_notes_list = person.get_note_list()
updated_note = False
for note_handle in previous_notes_list:
previous_note = self.db.get_note_from_handle(note_handle)
if previous_note.type == gen.lib.NoteType.PERSON:
previous_text = previous_note.get()
if note not in previous_text:
note = previous_text + "\n" + note
previous_note.set(note)
self.db.commit_note(previous_note, self.trans)
updated_note = True
break
if not updated_note:
# add new note here
new_note = gen.lib.Note()
new_note.handle = create_id()
new_note.type.set(gen.lib.NoteType.PERSON)
new_note.set(note)
self.db.add_note(new_note, self.trans)
person.add_note(new_note.handle)
if grampsid is not None:
person.gramps_id = grampsid
elif person_ref is not None:
if person_ref.startswith("[") and person_ref.endswith("]"):
person.gramps_id = self.db.id2user_format(person_ref[1:-1])
if (person.get_gender() == gen.lib.Person.UNKNOWN and
gender is not None):
gender = gender.lower()
if gender == gender_map[gen.lib.Person.MALE].lower():
gender = gen.lib.Person.MALE
elif gender == gender_map[gen.lib.Person.FEMALE].lower():
gender = gen.lib.Person.FEMALE
else:
gender = gen.lib.Person.UNKNOWN
person.set_gender(gender)
#########################################################
# add if new, replace if different
# Birth:
if birthdate is not None:
birthdate = _dp.parse(birthdate)
if birthplace is not None:
new, birthplace = self.get_or_create_place(birthplace)
if birthsource is not None:
new, birthsource = self.get_or_create_source(birthsource)
if birthdate or birthplace or birthsource:
new, birth = self.get_or_create_event(person,
gen.lib.EventType.BIRTH, birthdate,
birthplace, birthsource)
birth_ref = person.get_birth_ref()
if birth_ref is None:
# new
birth_ref = gen.lib.EventRef()
birth_ref.set_reference_handle( birth.get_handle())
person.set_birth_ref( birth_ref)
# Baptism:
if baptismdate is not None:
baptismdate = _dp.parse(baptismdate)
if baptismplace is not None:
new, baptismplace = self.get_or_create_place(baptismplace)
if baptismsource is not None:
new, baptismsource = self.get_or_create_source(baptismsource)
if baptismdate or baptismplace or baptismsource:
new, baptism = self.get_or_create_event(person,
gen.lib.EventType.BAPTISM, baptismdate,
baptismplace, baptismsource)
baptism_ref = get_primary_event_ref_from_type(self.db, person,
"Baptism")
if baptism_ref is None:
# new
baptism_ref = gen.lib.EventRef()
baptism_ref.set_reference_handle( baptism.get_handle())
person.add_event_ref( baptism_ref)
# Death:
if deathdate is not None:
deathdate = _dp.parse(deathdate)
if deathplace is not None:
new, deathplace = self.get_or_create_place(deathplace)
if deathsource is not None:
new, deathsource = self.get_or_create_source(deathsource)
if deathdate or deathplace or deathsource or deathcause:
new, death = self.get_or_create_event(person,
gen.lib.EventType.DEATH, deathdate, deathplace,
deathsource)
if deathcause:
death.set_description(deathcause)
self.db.commit_event(death, self.trans)
death_ref = person.get_death_ref()
if death_ref is None:
# new
death_ref = gen.lib.EventRef()
death_ref.set_reference_handle(death.get_handle())
person.set_death_ref(death_ref)
# Burial:
if burialdate is not None:
burialdate = _dp.parse(burialdate)
if burialplace is not None:
new, burialplace = self.get_or_create_place(burialplace)
if burialsource is not None:
new, burialsource = self.get_or_create_source(burialsource)
if burialdate or burialplace or burialsource:
new, burial = self.get_or_create_event(person,
gen.lib.EventType.BURIAL, burialdate,
burialplace, burialsource)
burial_ref = get_primary_event_ref_from_type(self.db, person,
"Burial")
if burial_ref is None:
# new
burial_ref = gen.lib.EventRef()
burial_ref.set_reference_handle( burial.get_handle())
person.add_event_ref( burial_ref)
if source:
# add, if new
new, source = self.get_or_create_source(source)
self.find_and_set_citation(person, source)
self.db.commit_person(person, self.trans)
def get_or_create_family(self, family_ref, husband, wife):
"Return the family object for the give family ID."
# if a gramps_id and exists:
LOG.debug("get_or_create_family")
if family_ref.startswith("[") and family_ref.endswith("]"):
id_ = self.db.fid2user_format(family_ref[1:-1])
family = self.db.get_family_from_gramps_id(id_)
if family:
# don't delete, only add
fam_husband_handle = family.get_father_handle()
fam_wife_handle = family.get_mother_handle()
if husband:
if husband.get_handle() != fam_husband_handle:
# this husband is not the same old one! Add him!
family.set_father_handle(husband.get_handle())
if wife:
if wife.get_handle() != fam_wife_handle:
# this wife is not the same old one! Add her!
family.set_mother_handle(wife.get_handle())
LOG.debug(" returning existing family")
return family
# if not, create one:
family = gen.lib.Family()
# was marked with a gramps_id, but didn't exist, so we'll use it:
if family_ref.startswith("[") and family_ref.endswith("]"):
id_ = self.db.fid2user_format(family_ref[1:-1])
family.set_gramps_id(id_)
# add it:
family.set_handle(self.db.create_id())
if husband:
family.set_father_handle(husband.get_handle())
husband.add_family_handle(family.get_handle())
if wife:
family.set_mother_handle(wife.get_handle())
wife.add_family_handle(family.get_handle())
if husband and wife:
family.set_relationship(gen.lib.FamilyRelType.MARRIED)
self.db.add_family(family, self.trans)
if husband:
self.db.commit_person(husband, self.trans)
if wife:
self.db.commit_person(wife, self.trans)
self.fam_count += 1
return family
def get_or_create_event(self, object_, type_, date=None, place=None,
source=None):
""" Add or find a type event on object """
# first, see if it exists
LOG.debug("get_or_create_event")
ref_list = object_.get_event_ref_list()
LOG.debug("refs: %s", ref_list)
# look for a match, and possible correction
for ref in ref_list:
event = self.db.get_event_from_handle(ref.ref)
LOG.debug(" compare event type %s == %s", int(event.get_type()),
type_)
if int(event.get_type()) == type_:
# Match! Let's update
if date:
event.set_date_object(date)
if place:
event.set_place_handle(place.get_handle())
if source:
self.find_and_set_citation(event, source)
self.db.commit_event(event, self.trans)
LOG.debug(" returning existing event")
return (0, event)
# else create it:
LOG.debug(" creating event")
event = gen.lib.Event()
if type_:
event.set_type(gen.lib.EventType(type_))
if date:
event.set_date_object(date)
if place:
event.set_place_handle(place.get_handle())
if source:
self.find_and_set_citation(event, source)
self.db.add_event(event, self.trans)
return (1, event)
def create_person(self):
""" Used to create a new person we know doesn't exist """
person = gen.lib.Person()
self.db.add_person(person, self.trans)
self.indi_count += 1
return person
def get_or_create_place(self, place_name):
"Return the requested place object tuple-packed with a new indicator."
LOG.debug("get_or_create_place: looking for: %s", place_name)
for place_handle in self.db.iter_place_handles():
place = self.db.get_place_from_handle(place_handle)
if place.get_title() == place_name:
return (0, place)
place = gen.lib.Place()
place.set_title(place_name)
self.db.add_place(place, self.trans)
return (1, place)
def get_or_create_source(self, source_text):
"Return the requested source object tuple-packed with a new indicator."
source_list = self.db.get_source_handles(sort_handles=False)
LOG.debug("get_or_create_source: list: %s", source_list)
LOG.debug("get_or_create_source: looking for: %s", source_text)
for source_handle in source_list:
source = self.db.get_source_from_handle(source_handle)
if source.get_title() == source_text:
LOG.debug(" returning existing source")
return (0, source)
LOG.debug(" creating source")
source = gen.lib.Source()
source.set_title(source_text)
self.db.add_source(source, self.trans)
return (1, source)
def find_and_set_citation(self, obj, source):
# look for the source in the existing citations for the object
LOG.debug("find_and_set_citation: looking for source: %s",
source.get_gramps_id())
for citation_handle in obj.get_citation_list():
citation = self.db.get_citation_from_handle(citation_handle)
LOG.debug("find_and_set_citation: existing citation: %s",
citation.get_gramps_id())
poss_source = self.db.get_source_from_handle(
citation.get_reference_handle())
LOG.debug(" compare source %s == %s", source.get_gramps_id(),
poss_source.get_gramps_id())
if poss_source.get_handle() == source.get_handle():
# The source is already cited
LOG.debug(" source already cited")
return
# we couldn't find an appropriate citation, so we have to create one.
citation = gen.lib.Citation()
LOG.debug(" creating citation")
citation.set_reference_handle(source.get_handle())
self.db.add_citation(citation, self.trans)
LOG.debug(" created citation, citation %s %s" %
(citation, citation.get_gramps_id()))
obj.add_citation(citation.get_handle())