示例#1
0
 def create_tag(self, tag_name, tag_color):
     """
     Create a tag if it doesn't already exist.
     """
     tag = self.db.get_tag_from_name(tag_name)
     if tag is None:
         tag = Tag()
         tag.set_name(tag_name)
         if tag_color is not None:
             tag.set_color(tag_color)
         tag.set_priority(self.db.get_number_of_tags())
         tag_handle = self.db.add_tag(tag, self.trans)
     else:
         tag_handle = tag.get_handle()
     return tag_handle
示例#2
0
    def on_ok_clicked(self, obj):

        clean = self.clear_button.get_active()
        copy = self.copy_button.get_active()
        tag = self.tag_button.get_active()
        if not (copy or clean or tag):
            return

        self.db.disable_signals()

        if tag:
            tag_name = _('Legacy place')
            # start the db transaction
            with DbTxn("Tag for place titles", self.db) as self.tag_trans:

                mark = self.db.get_tag_from_name(tag_name)
                if not mark:
                    # create the tag if it doesn't already exist
                    mark = Tag()
                    mark.set_name(tag_name)
                    mark.set_priority(self.db.get_number_of_tags())
                    tag_handle = self.db.add_tag(mark, self.tag_trans)
                else:
                    tag_handle = mark.get_handle()

        with DbTxn(_("Modify Place titles"), self.db, batch=True) as trans:
            for row in self.model:
                if row[1] == True:
                    place = self.db.get_place_from_handle(row[0])
                    if copy:
                        self.create_note(place, row[2], trans)
                    if clean:
                        place.set_title("")
                    if tag:
                        place.add_tag(tag_handle)
                    self.db.commit_place(place, trans)

        self.db.enable_signals()
        self.db.request_rebuild()
        self.close()
示例#3
0
    def applyTagClicked(self, button) :
        progress    = None
        rows        = self.treeSelection.count_selected_rows()
        tag_name    = str(self.tagcombo.get_active_text())

        # start the db transaction
        with DbTxn("Tag not related", self.db) as transaction:

            tag = self.db.get_tag_from_name(tag_name)
            if not tag:
                # create the tag if it doesn't already exist
                tag = Tag()
                tag.set_name(tag_name)
                tag.set_priority(self.db.get_number_of_tags())
                tag_handle = self.db.add_tag(tag, transaction)
            else:
                tag_handle = tag.get_handle()

            # if more than 1 person is selected, use a progress indicator
            if rows > 1:
                progress = ProgressMeter(self.title,_('Starting'),
                                         parent=self.window)
                progress.set_pass(
                    # translators: leave all/any {...} untranslated
                    #TRANS: no singular form needed, as rows is always > 1
                    ngettext("Setting tag for {number_of} person",
                             "Setting tag for {number_of} people",
                             rows).format(number_of=rows),
                    rows)


            # iterate through all of the selected rows
            (model, paths) = self.treeSelection.get_selected_rows()

            for path in paths:
                if progress:
                    progress.step()

                # for the current row, get the GID and the person from the database
                iter        = self.model.get_iter(path)
                personGid   = self.model.get_value(iter, 1)
                person      = self.db.get_person_from_gramps_id(personGid)

                # add the tag to the person
                person.add_tag(tag_handle)

                # save this change
                self.db.commit_person(person, transaction)


        # refresh the tags column
        self.treeView.set_model(None)
        for path in paths:
            iter        = self.model.get_iter(path)
            personGid   = self.model.get_value(iter, 1)
            person      = self.db.get_person_from_gramps_id(personGid)
            self.model.set_value(iter, 3, self.get_tag_list(person))
        self.treeView.set_model(self.model)
        self.treeView.expand_all()

        if progress:
            progress.close()
示例#4
0
class CSVParser:
    """Class to read data in CSV format from a file object."""
    def __init__(self, dbase, user, default_tag_format=None):
        self.db = dbase
        self.user = user
        self.trans = None
        self.lineno = 0
        self.index = 0
        self.fam_count = 0
        self.indi_count = 0
        self.place_count = 0
        self.pref  = {} # person ref, internal to this sheet
        self.fref  = {} # family ref, internal to this sheet
        self.placeref = {}
        self.place_types = {}
        # Build reverse dictionary, name to type number
        for items in PlaceType().get_map().items(): # (0, 'Custom')
            self.place_types[items[1]] = items[0]
            self.place_types[items[1].lower()] = items[0]
            if _(items[1]) != items[1]:
                self.place_types[_(items[1])] = items[0]
        # Add custom types:
        for custom_type in self.db.get_place_types():
            self.place_types[custom_type] = 0
            self.place_types[custom_type.lower()] = 0
        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 or Place|Title"), "title", _("Person or Place|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")),
            "birthplace_id": ("Birthplaceid", "Birth place id", _("Birth place id"),
                              "birthplaceid", "birth_place_id", "birth place id", _("birth place id"),
                              "birthplace_id"),
            "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")),
            "baptismplace_id": ("Baptismplaceid", "Baptism place id",
                                _("Baptism place id"), "baptismplaceid", "baptism place id",
                                _("baptism place id"), "baptism_place_id",
                                "baptismplace_id"),
            "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")),
            "burialplace_id": ("Burialplaceid", "Burial place id", _("Burial place id"),
                               "burialplaceid", "burial place id", _("burial place id"),
                               "burial_place_id", "burialplace_id"),
            "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")),
            "deathplace_id": ("Deathplaceid", "Death place id", _("Death place id"),
                              "deathplaceid", "death_place_id", "death place id", _("death place id"),
                              "death_place_id", "deathplace_id"),
            "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")),
            "place_id": ("Placeid", "place id", "Place id", "place_id", "placeid"),
            "name": ("Name", _("Name"), "name", _("name")),
            "type": ("Type", _("Type"), "type", _("type")),
            "latitude": ("Latitude", _("latitude"), "latitude", _("latitude")),
            "longitude": ("Longitude", _("Longitude"), "longitude", _("longitude")),
            "code": ("Code", _("Code"), "code", _("code")),
            "enclosed_by": ("Enclosed by", _("Enclosed by"), "enclosed by", _("enclosed by"),
                            "enclosed_by", _("enclosed_by"), "Enclosed_by", _("Enclosed_by"),
                            "enclosedby")
        }
        lab2col_dict = []
        for key in list(column2label.keys()):
            for val in column2label[key]:
                lab2col_dict.append((val, key))
        self.label2column = dict(lab2col_dict)
        if default_tag_format:
            name = time.strftime(default_tag_format)
            tag = self.db.get_tag_from_name(name)
            if tag:
                self.default_tag = tag
            else:
                self.default_tag = Tag()
                self.default_tag.set_name(name)
        else:
            self.default_tag = None

    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."
        try:
            data = [[r.strip() for r in row] for row in csv.reader(filehandle)]
        except csv.Error as err:
            self.user.notify_error(_('format error: line %(line)d: %(zero)s') % {
                        'line' : 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
        elif type_ == "place":
            if id_.startswith("[") and id_.endswith("]"):
                id_ = self.db.id2user_format(id_[1:-1])
                db_lookup = self.db.get_place_from_gramps_id(id_)
                if db_lookup is None:
                    return self.lookup(type_, id_)
                else:
                    return db_lookup
            elif id_.lower() in self.placeref:
                return self.placeref[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_
        elif type_ == "place":
            id_ = self.db.pid2user_format(id_)
            self.placeref[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
        """
        progress_title = _('CSV Import')
        with self.user.progress(progress_title,
                _('Reading data...'), 1) as step:
            data = self.read_csv(filehandle)

        with self.user.progress(progress_title,
                _('Importing data...'), len(data)) as step:
            tym = time.time()
            self.db.disable_signals()
            with DbTxn(_("CSV import"), self.db, batch=True) as self.trans:
                if self.default_tag and self.default_tag.handle is None:
                    self.db.add_tag(self.default_tag, self.trans)
                self._parse_csv_data(data, step)
            self.db.enable_signals()
            self.db.request_rebuild()
            tym = time.time() - tym
            # translators: leave all/any {...} untranslated
            msg = ngettext('Import Complete: {number_of} second',
                           'Import Complete: {number_of} seconds', tym
                          ).format(number_of=tym)
            LOG.debug(msg)
            LOG.debug("New Families: %d" % self.fam_count)
            LOG.debug("New Individuals: %d" % self.indi_count)

    def _parse_csv_data(self, data, step):
        """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.place_count = 0
        self.pref  = {} # person ref, internal to this sheet
        self.fref  = {} # family ref, internal to this sheet
        self.placeref = {}
        header = None
        line_number = 0
        for row in data:
            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
            # four 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)
            elif "place" in header:
                self._parse_place(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")
        marriageplace_id = rd(line_number, row, col, "place_id")
        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() == Person.UNKNOWN:
                husband.set_gender(Person.MALE)
                self.db.commit_person(husband, self.trans)
        if wife:
            # this is just a guess, if unknown
            if wife.get_gender() == Person.UNKNOWN:
                wife.set_gender(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 and marriageplace_id:
            raise Error("Error in marriage: can't have a place and place_id")
        if marriageplace:
            # add, if new
            new, marriageplace = self.get_or_create_place(marriageplace)
        elif marriageplace_id:
            # better exist already, locally or in database:
            marriageplace = self.lookup("place", marriageplace_id)
        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,
                    EventType.MARRIAGE, marriagedate,
                    marriageplace, marriagesource)
            if new:
                mar_ref = 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 == 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 = Note()
                    new_note.handle = create_id()
                    new_note.type.set(NoteType.EVENT)
                    new_note.set(note)
                    if self.default_tag:
                        new_note.add_tag(self.default_tag.handle)
                    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 = 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[Person.MALE].lower():
                gender = Person.MALE
            elif gender == gender_map[Person.FEMALE].lower():
                gender = Person.FEMALE
            else:
                gender = 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 == 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 = Note()
                new_note.handle = create_id()
                new_note.type.set(NoteType.PERSON)
                new_note.set(note)
                if self.default_tag:
                    new_note.add_tag(self.default_tag.handle)
                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")
        birthplace_id  = rd(line_number, row, col, "birthplace_id")
        birthdate   = rd(line_number, row, col, "birthdate")
        birthsource = rd(line_number, row, col, "birthsource")
        baptismplace  = rd(line_number, row, col, "baptismplace")
        baptismplace_id  = rd(line_number, row, col, "baptismplace_id")
        baptismdate   = rd(line_number, row, col, "baptismdate")
        baptismsource = rd(line_number, row, col, "baptismsource")
        burialplace  = rd(line_number, row, col, "burialplace")
        burialplace_id  = rd(line_number, row, col, "burialplace_id")
        burialdate   = rd(line_number, row, col, "burialdate")
        burialsource = rd(line_number, row, col, "burialsource")
        deathplace  = rd(line_number, row, col, "deathplace")
        deathplace_id  = rd(line_number, row, col, "deathplace_id")
        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 = Name()
            name.set_type(NameType(NameType.BIRTH))
            name.set_first_name(firstname)
            surname_obj = 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 == 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 = Note()
                new_note.handle = create_id()
                new_note.type.set(NoteType.PERSON)
                new_note.set(note)
                if self.default_tag:
                    new_note.add_tag(self.default_tag.handle)
                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() == Person.UNKNOWN and
                gender is not None):
            gender = gender.lower()
            if gender == gender_map[Person.MALE].lower():
                gender = Person.MALE
            elif gender == gender_map[Person.FEMALE].lower():
                gender = Person.FEMALE
            else:
                gender = Person.UNKNOWN
            person.set_gender(gender)
        #########################################################
        # add if new, replace if different
        # Birth:
        if birthdate is not None:
            birthdate = _dp.parse(birthdate)
        if birthplace and birthplace_id:
            raise Error("Error in person: can't have a birthplace and birthplace_id")
        if birthplace is not None:
            new, birthplace = self.get_or_create_place(birthplace)
        elif birthplace_id:
            # better exist already, locally or in database:
            birthplace = self.lookup("place", birthplace_id)
        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,
                 EventType.BIRTH, birthdate,
                 birthplace, birthsource)
            birth_ref = person.get_birth_ref()
            if birth_ref is None:
                # new
                birth_ref = 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 and baptismplace_id:
            raise Error("Error in person: can't have a baptismplace and baptismplace_id")
        if baptismplace is not None:
            new, baptismplace = self.get_or_create_place(baptismplace)
        elif baptismplace_id:
            # better exist already, locally or in database:
            baptismplace = self.lookup("place", baptismplace_id)
        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,
                 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 = 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 and deathplace_id:
            raise Error("Error in person: can't have a deathplace and deathplace_id")
        if deathplace is not None:
            new, deathplace = self.get_or_create_place(deathplace)
        elif deathplace_id:
            # better exist already, locally or in database:
            deathplace = self.lookup("place", deathplace_id)
        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,
                    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 = 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 and burialplace_id:
            raise Error("Error in person: can't have a burialplace and burialplace_id")
        if burialplace is not None:
            new, burialplace = self.get_or_create_place(burialplace)
        elif burialplace_id:
            # better exist already, locally or in database:
            burialplace = self.lookup("place", burialplace_id)
        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,
                 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 = 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 _parse_place(self, line_number, row, col):
        "Parse the content of a Place line."
        place_id = rd(line_number, row, col, "place")
        place_title = rd(line_number, row, col, "title")
        place_name = rd(line_number, row, col, "name")
        place_type_str = rd(line_number, row, col, "type")
        place_latitude = rd(line_number, row, col, "latitude")
        place_longitude = rd(line_number, row, col, "longitude")
        place_code = rd(line_number, row, col, "code")
        place_enclosed_by_id = rd(line_number, row, col, "enclosed_by")
        place_date = rd(line_number, row, col, "date")
        #########################################################
        # if this place already exists, don't create it
        place = self.lookup("place", place_id)
        if place is None:
            # new place
            place = self.create_place()
            self.storeup("place", place_id.lower(), place)
        if place_title is not None:
            place.title = place_title
        if place_name is not None:
            place.name = PlaceName(value=place_name)
        if place_type_str is not None:
            place.place_type = self.get_place_type(place_type_str)
        if place_latitude is not None:
            place.lat = place_latitude
        if place_longitude is not None:
            place.long = place_longitude
        if place_code is not None:
            place.code = place_code
        if place_enclosed_by_id is not None:
            place_enclosed_by = self.lookup("place", place_enclosed_by_id)
            if place_enclosed_by is None:
                raise Exception("cannot enclose %s in %s as it doesn't exist" % (place.gramps_id, place_enclosed_by_id))
            if not place_enclosed_by.handle in place.placeref_list:
                placeref = PlaceRef()
                placeref.ref = place_enclosed_by.handle
                if place_date:
                    placeref.date = _dp.parse(place_date)
                place.placeref_list.append(placeref)
        #########################################################
        self.db.commit_place(place, self.trans)

    def get_place_type(self, place_type_str):
        if place_type_str in self.place_types:
            return PlaceType((self.place_types[place_type_str], place_type_str))
        else:
            # New custom type:
            return PlaceType((0, place_type_str))

    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 = 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(create_id())
        if self.default_tag:
            family.add_tag(self.default_tag.handle)
        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(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 = Event()
        if type_:
            event.set_type(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 = Person()
        if self.default_tag:
            person.add_tag(self.default_tag.handle)
        self.db.add_person(person, self.trans)
        self.indi_count += 1
        return person

    def create_place(self):
        """ Used to create a new person we know doesn't exist """
        place = Place()
        if self.default_tag:
            place.add_tag(self.default_tag.handle)
        self.db.add_place(place, self.trans)
        self.place_count += 1
        return place

    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)
            place_title = place_displayer.display(self.db, place)
            if place_title == place_name:
                return (0, place)
        place = Place()
        place.set_title(place_name)
        place.name = PlaceName(value=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 = 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 = 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())
示例#5
0
    def applyTagClicked(self, button) :
        progress = None
        rows = self.treeSelection.count_selected_rows()
        tag_name = str(self.tagcombo.get_active_text())

        # start the db transaction
        with DbTxn("Tag not related", self.db) as transaction:

            tag = self.db.get_tag_from_name(tag_name)
            if not tag:
                # create the tag if it doesn't already exist
                tag = Tag()
                tag.set_name(tag_name)
                tag.set_priority(self.db.get_number_of_tags())
                tag_handle = self.db.add_tag(tag, transaction)
            else:
                tag_handle = tag.get_handle()

            # if more than 1 person is selected, use a progress indicator
            if rows > 1:
                progress = ProgressMeter(self.title, _('Starting'),
                                         parent=self.window)
                progress.set_pass(
                    # translators: leave all/any {...} untranslated
                    #TRANS: no singular form needed, as rows is always > 1
                    ngettext("Setting tag for {number_of} person",
                             "Setting tag for {number_of} people",
                             rows).format(number_of=rows),
                    rows)


            # iterate through all of the selected rows
            (model, paths) = self.treeSelection.get_selected_rows()

            for path in paths:
                if progress:
                    progress.step()

                # for the current row, get the GID and the person from the database
                iter = self.model.get_iter(path)
                personGid = self.model.get_value(iter, 1)
                person = self.db.get_person_from_gramps_id(personGid)

                # add the tag to the person
                person.add_tag(tag_handle)

                # save this change
                self.db.commit_person(person, transaction)


        # refresh the tags column
        self.treeView.set_model(None)
        for path in paths:
            iter = self.model.get_iter(path)
            personGid = self.model.get_value(iter, 1)
            person = self.db.get_person_from_gramps_id(personGid)
            self.model.set_value(iter, 3, self.get_tag_list(person))
        self.treeView.set_model(self.model)
        self.treeView.expand_all()

        if progress:
            progress.close()
示例#6
0
class CSVParser(object):
    """Class to read data in CSV format from a file object."""
    def __init__(self, dbase, user, default_tag_format=None):
        self.db = dbase
        self.user = user
        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 list(column2label.keys()):
            for val in column2label[key]:
                lab2col_dict.append((val, key))
        self.label2column = dict(lab2col_dict)
        if default_tag_format:
            name = time.strftime(default_tag_format)
            tag = self.db.get_tag_from_name(name)
            if tag:
                self.default_tag = tag
            else:
                self.default_tag = Tag()
                self.default_tag.set_name(name)
        else:
            self.default_tag = None

    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 as err:
            self.user.notify_error(_('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
        """
        progress_title = _('CSV Import')
        with self.user.progress(progress_title, 
                _('Reading data...'), 1) as step:
            data = self.read_csv(filehandle)

        with self.user.progress(progress_title, 
                _('Importing data...'), len(data)) as step:
            tym = time.time()
            self.db.disable_signals()
            with DbTxn(_("CSV import"), self.db, batch=True) as self.trans:
                if self.default_tag and self.default_tag.handle is None:
                    self.db.add_tag(self.default_tag, self.trans)
                self._parse_csv_data(data, step)
            self.db.enable_signals()
            self.db.request_rebuild()
            tym = time.time() - tym
            # translators: leave all/any {...} untranslated
            msg = ngettext('Import Complete: {number_of} second',
                           'Import Complete: {number_of} seconds', tym
                          ).format(number_of=tym)
            LOG.debug(msg)
            LOG.debug("New Families: %d" % self.fam_count)
            LOG.debug("New Individuals: %d" % self.indi_count)

    def _parse_csv_data(self, data, step):
        """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:
            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() == Person.UNKNOWN:
                husband.set_gender(Person.MALE)
                self.db.commit_person(husband, self.trans)
        if wife:
            # this is just a guess, if unknown
            if wife.get_gender() == Person.UNKNOWN:
                wife.set_gender(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,
                    EventType.MARRIAGE, marriagedate,
                    marriageplace, marriagesource)
            if new:
                mar_ref = 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 == 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 = Note()
                    new_note.handle = create_id()
                    new_note.type.set(NoteType.EVENT)
                    new_note.set(note)
                    if self.default_tag:
                        new_note.add_tag(self.default_tag.handle)
                    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 = 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[Person.MALE].lower():
                gender = Person.MALE
            elif gender == gender_map[Person.FEMALE].lower():
                gender = Person.FEMALE
            else:
                gender = 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 == 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 = Note()
                new_note.handle = create_id()
                new_note.type.set(NoteType.PERSON)
                new_note.set(note)
                if self.default_tag:
                    new_note.add_tag(self.default_tag.handle)
                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 = Name()
            name.set_type(NameType(NameType.BIRTH))
            name.set_first_name(firstname)
            surname_obj = 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 == 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 = Note()
                new_note.handle = create_id()
                new_note.type.set(NoteType.PERSON)
                new_note.set(note)
                if self.default_tag:
                    new_note.add_tag(self.default_tag.handle)
                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() == Person.UNKNOWN and
                gender is not None):
            gender = gender.lower()
            if gender == gender_map[Person.MALE].lower():
                gender = Person.MALE
            elif gender == gender_map[Person.FEMALE].lower():
                gender = Person.FEMALE
            else:
                gender = 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, 
                 EventType.BIRTH, birthdate, 
                 birthplace, birthsource)
            birth_ref = person.get_birth_ref()
            if birth_ref is None:
                # new
                birth_ref = 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, 
                 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 = 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,
                    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 = 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, 
                 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 = 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 = 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(create_id())
        if self.default_tag:
            family.add_tag(self.default_tag.handle)
        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(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 = Event()
        if type_:
            event.set_type(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 = Person()
        if self.default_tag:
            person.add_tag(self.default_tag.handle)
        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)
            place_title = place_displayer.display(self.db, place)
            if place_title == place_name:
                return (0, place)
        place = 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 = 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 = 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())