def add(self, *obj): person = Person() # attempt to get the current surname (model, pathlist) = self.selection.get_selected_rows() name = Name() #the editor requires a surname name.add_surname(Surname()) name.set_primary_surname(0) basepers = None if len(pathlist) == 1: path = pathlist[0] pathids = path.get_indices() if len(pathids) == 1: path = Gtk.TreePath((pathids[0], 0)) iter_ = model.get_iter(path) handle = model.get_handle_from_iter(iter_) basepers = self.dbstate.db.get_person_from_handle(handle) if basepers: preset_name(basepers, name) person.set_primary_name(name) try: EditPerson(self.dbstate, self.uistate, [], person) except WindowActiveError: pass
def add_mother_clicked(self, obj): person = Person() person.set_gender(Person.FEMALE) autoname = config.get("behavior.surname-guessing") # _("Father's surname"), # _("None"), # _("Combination of mother's and father's surname"), # _("Icelandic style"), if autoname == 2: name = self.latin_american_child("mother") else: name = self.no_name() person.set_primary_name(name) EditPerson(self.dbstate, self.uistate, self.track, person, self.new_mother_added)
def add_mother_clicked(self, obj): person = Person() person.set_gender(Person.FEMALE) autoname = config.get('behavior.surname-guessing') #_("Father's surname"), #_("None"), #_("Combination of mother's and father's surname"), #_("Icelandic style"), if autoname == 2: name = self.latin_american_child("mother") else: name = self.no_name() person.set_primary_name(name) EditPerson(self.dbstate, self.uistate, self.track, person, self.new_mother_added)
def add_button_clicked(self, obj=None): person = Person() autoname = config.get("behavior.surname-guessing") # _("Father's surname"), # _("None"), # _("Combination of mother's and father's surname"), # _("Icelandic style"), if autoname == 0: name = self.north_american() elif autoname == 2: name = self.latin_american() else: name = self.no_name() person.set_primary_name(name) EditPerson(self.dbstate, self.uistate, self.track, person, self.new_child_added)
def add_button_clicked(self, obj=None): person = Person() autoname = config.get('behavior.surname-guessing') #_("Father's surname"), #_("None"), #_("Combination of mother's and father's surname"), #_("Icelandic style"), if autoname == 0: name = self.north_american() elif autoname == 2: name = self.latin_american() else: name = self.no_name() person.set_primary_name(name) EditPerson(self.dbstate, self.uistate, self.track, person, self.new_child_added)
def add_child_to_fam(self, obj, event, handle): if button_activated(event, _LEFT_BUTTON): callback = lambda x: self.callback_add_child(x, handle) person = Person() name = Name() #the editor requires a surname name.add_surname(Surname()) name.set_primary_surname(0) family = self.dbstate.db.get_family_from_handle(handle) father = self.dbstate.db.get_person_from_handle( family.get_father_handle()) if father: preset_name(father, name) person.set_primary_name(name) try: EditPerson(self.dbstate, self.uistate, [], person, callback=callback) except WindowActiveError: pass
def run_tool(self): self.progress = ProgressMeter(_('Running Date Test'),'', parent=self.parent_window) self.progress.set_pass(_('Generating dates'), 4) dates = [] # first some valid dates calendar = Date.CAL_GREGORIAN for quality in (Date.QUAL_NONE, Date.QUAL_ESTIMATED, Date.QUAL_CALCULATED): for modifier in (Date.MOD_NONE, Date.MOD_BEFORE, Date.MOD_AFTER, Date.MOD_ABOUT): for slash1 in (False,True): for month in range(0,13): for day in (0,5,27): if not month and day: continue d = Date() d.set(quality,modifier,calendar,(day,month,1789,slash1),"Text comment") dates.append( d) for modifier in (Date.MOD_RANGE, Date.MOD_SPAN): for slash1 in (False,True): for slash2 in (False,True): for month in range(0,13): for day in (0,5,27): if not month and day: continue d = Date() d.set(quality,modifier,calendar,(day,month,1789,slash1,day,month,1876,slash2),"Text comment") dates.append( d) if not month: continue d = Date() d.set(quality,modifier,calendar,(day,month,1789,slash1,day,13-month,1876,slash2),"Text comment") dates.append( d) if not day: continue d = Date() d.set(quality,modifier,calendar,(day,month,1789,slash1,32-day,month,1876,slash2),"Text comment") dates.append( d) d = Date() d.set(quality,modifier,calendar,(day,month,1789,slash1,32-day,13-month,1876,slash2),"Text comment") dates.append( d) modifier = Date.MOD_TEXTONLY d = Date() d.set(quality,modifier,calendar,Date.EMPTY, "This is a textual date") dates.append( d) self.progress.step() # test invalid dates #dateval = (4,7,1789,False,5,8,1876,False) #for l in range(1,len(dateval)): # d = Date() # try: # d.set(Date.QUAL_NONE,Date.MOD_NONE, # Date.CAL_GREGORIAN,dateval[:l],"Text comment") # dates.append( d) # except DateError, e: # d.set_as_text("Date identified value correctly as invalid.\n%s" % e) # dates.append( d) # except: # d = Date() # d.set_as_text("Date.set Exception %s" % ("".join(traceback.format_exception(*sys.exc_info())),)) # dates.append( d) #for l in range(1,len(dateval)): # d = Date() # try: # d.set(Date.QUAL_NONE,Date.MOD_SPAN,Date.CAL_GREGORIAN,dateval[:l],"Text comment") # dates.append( d) # except DateError, e: # d.set_as_text("Date identified value correctly as invalid.\n%s" % e) # dates.append( d) # except: # d = Date() # d.set_as_text("Date.set Exception %s" % ("".join(traceback.format_exception(*sys.exc_info())),)) # dates.append( d) #self.progress.step() #d = Date() #d.set(Date.QUAL_NONE,Date.MOD_NONE, # Date.CAL_GREGORIAN,(44,7,1789,False),"Text comment") #dates.append( d) #d = Date() #d.set(Date.QUAL_NONE,Date.MOD_NONE, # Date.CAL_GREGORIAN,(4,77,1789,False),"Text comment") #dates.append( d) #d = Date() #d.set(Date.QUAL_NONE,Date.MOD_SPAN, # Date.CAL_GREGORIAN, # (4,7,1789,False,55,8,1876,False),"Text comment") #dates.append( d) #d = Date() #d.set(Date.QUAL_NONE,Date.MOD_SPAN, # Date.CAL_GREGORIAN, # (4,7,1789,False,5,88,1876,False),"Text comment") #dates.append( d) with DbTxn(_("Date Test Plugin"), self.db, batch=True) as self.trans: self.db.disable_signals() self.progress.set_pass(_('Generating dates'), len(dates)) # create pass and fail tags pass_handle = self.create_tag(_('Pass'), '#0000FFFF0000') fail_handle = self.create_tag(_('Fail'), '#FFFF00000000') # now add them as birth to new persons i = 1 for dateval in dates: person = Person() surname = Surname() surname.set_surname("DateTest") name = Name() name.add_surname(surname) name.set_first_name("Test %d" % i) person.set_primary_name(name) self.db.add_person(person, self.trans) bevent = Event() bevent.set_type(EventType.BIRTH) bevent.set_date_object(dateval) bevent.set_description("Date Test %d (source)" % i) bevent_h = self.db.add_event(bevent, self.trans) bevent_ref = EventRef() bevent_ref.set_reference_handle(bevent_h) # for the death event display the date as text and parse it back to a new date ndate = None try: datestr = _dd.display( dateval) try: ndate = _dp.parse( datestr) if not ndate: ndate = Date() ndate.set_as_text("DateParser None") person.add_tag(fail_handle) else: person.add_tag(pass_handle) except: ndate = Date() ndate.set_as_text("DateParser Exception %s" % ("".join(traceback.format_exception(*sys.exc_info())),)) person.add_tag(fail_handle) else: person.add_tag(pass_handle) except: ndate = Date() ndate.set_as_text("DateDisplay Exception: %s" % ("".join(traceback.format_exception(*sys.exc_info())),)) person.add_tag(fail_handle) if dateval.get_modifier() != Date.MOD_TEXTONLY \ and ndate.get_modifier() == Date.MOD_TEXTONLY: # parser was unable to correctly parse the string ndate.set_as_text( "TEXTONLY: "+ndate.get_text()) person.add_tag(fail_handle) if dateval.get_modifier() == Date.MOD_TEXTONLY \ and dateval.get_text().count("Traceback") \ and pass_handle in person.get_tag_list(): person.add_tag(fail_handle) devent = Event() devent.set_type(EventType.DEATH) devent.set_date_object(ndate) devent.set_description("Date Test %d (result)" % i) devent_h = self.db.add_event(devent, self.trans) devent_ref = EventRef() devent_ref.set_reference_handle(devent_h) person.set_birth_ref(bevent_ref) person.set_death_ref(devent_ref) self.db.commit_person(person, self.trans) i = i + 1 self.progress.step() self.db.enable_signals() self.db.request_rebuild() self.progress.close()
class VCardParser: """Class to read data in VCard format from a file.""" DATE_RE = re.compile(r'^(\d{4}-\d{1,2}-\d{1,2})|(?:(\d{4})-?(\d\d)-?(\d\d))') GROUP_RE = re.compile(r'^(?:[-0-9A-Za-z]+\.)?(.+)$') # see RFC 2425 sec5.8.2 ESCAPE_CHAR = '\\' TOBE_ESCAPED = ['\\', ',', ';'] # order is important LINE_CONTINUATION = [' ', '\t'] @staticmethod def name_value_split(data): """Property group.name:value split is on first unquoted colon.""" colon_idx = data.find(':') if colon_idx < 1: return () quote_count = data.count('"', 0, colon_idx) while quote_count % 2 == 1: colon_idx = data.find(':', colon_idx + 1) quote_count = data.count('"', 0, colon_idx) group_name, value = data[:colon_idx], data[colon_idx + 1:] name_parts = VCardParser.GROUP_RE.match(group_name) return (name_parts.group(1), value) @staticmethod def unesc(data): """Remove VCard escape sequences.""" if type(data) == type('string'): for char in reversed(VCardParser.TOBE_ESCAPED): data = data.replace(VCardParser.ESCAPE_CHAR + char, char) return data elif type(data) == type([]): return list(map(VCardParser.unesc, data)) else: raise TypeError("VCard unescaping is not implemented for " "data type %s." % str(type(data))) @staticmethod def count_escapes(strng): """Count the number of escape characters at the end of a string.""" count = 0 for char in reversed(strng): if char != VCardParser.ESCAPE_CHAR: return count count += 1 return count @staticmethod def split_unescaped(strng, sep): """Split on sep if sep is unescaped.""" strng_parts = strng.split(sep) for i in reversed(range(len(strng_parts[:]))): if VCardParser.count_escapes(strng_parts[i]) % 2 == 1: # the sep was escaped so undo split appendix = strng_parts.pop(i + 1) strng_parts[i] += sep + appendix return strng_parts def __init__(self, dbase): self.database = dbase self.formatted_name = '' self.name_parts = '' self.next_line = None self.trans = None self.version = None self.person = None self.errors = [] self.number_of_errors = 0 def __get_next_line(self, filehandle): """ Read and return the line with the next property of the VCard. Also if it spans multiple lines (RFC 2425 sec.5.8.1). """ line = self.next_line self.next_line = filehandle.readline() self.line_num = self.line_num + 1 while self.next_line and self.next_line[0] in self.LINE_CONTINUATION: line = line.rstrip("\n") # TODO perhaps next lines superflous because of rU open parameter? if len(line) > 0 and line[-1] == "\r": line = line[:-1] line += self.next_line[1:] self.next_line = filehandle.readline() self.line_num = self.line_num + 1 if line: line = line.strip() else: line = None return line def __add_msg(self, problem, line=None): if problem != "": self.number_of_errors += 1 if line: message = _("Line %(line)5d: %(prob)s\n") % {"line": line, "prob": problem} else: message = problem + "\n" self.errors.append(message) def parse(self, filehandle, user): """ Prepare the database and parse the input file. :param filehandle: open file handle positioned at start of the file """ tym = time.time() self.person = None self.database.disable_signals() with DbTxn(_("vCard import"), self.database, batch=True) as self.trans: self._parse_vCard_file(filehandle) self.database.enable_signals() self.database.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) if self.number_of_errors == 0: message = _("VCARD import report: No errors detected") else: message = _("VCARD import report: %s errors detected\n") % \ self.number_of_errors if hasattr(user.uistate, 'window'): parent_window = user.uistate.window else: parent_window = None user.info(message, "".join(self.errors), parent=parent_window, monospaced=True) def _parse_vCard_file(self, filehandle): """Read each line of the input file and act accordingly.""" self.next_line = filehandle.readline() self.line_num = 1 while True: line = self.__get_next_line(filehandle) if line is None: break if line == "": continue if line.find(":") == -1: continue line_parts = self.name_value_split(line) if not line_parts: continue # No check for escaped ; because only fields[0] is used. fields = line_parts[0].split(";") property_name = fields[0].upper() if property_name == "BEGIN": self.next_person() elif property_name == "END": self.finish_person() elif property_name == "VERSION": self.check_version(fields, line_parts[1]) elif property_name == "FN": self.add_formatted_name(fields, line_parts[1]) elif property_name == "N": self.add_name_parts(fields, line_parts[1]) elif property_name == "NICKNAME": self.add_nicknames(fields, line_parts[1]) elif property_name == "SORT-STRING": self.add_sortas(fields, line_parts[1]) elif property_name == "ADR": self.add_address(fields, line_parts[1]) elif property_name == "TEL": self.add_phone(fields, line_parts[1]) elif property_name == "BDAY": self.add_birthday(fields, line_parts[1]) elif property_name == "ROLE": self.add_occupation(fields, line_parts[1]) elif property_name == "URL": self.add_url(fields, line_parts[1]) elif property_name == "EMAIL": self.add_email(fields, line_parts[1]) elif property_name == "X-GENDER" or property_name == "GENDER": # VCard 3.0 only has X-GENDER, GENDER is 4.0 syntax, # but we want to be robust here. self.add_gender(fields, line_parts[1]) elif property_name == "PRODID": # Included cause VCards made by Gramps have this prop. pass else: self.__add_msg(_("Token >%(token)s< unknown. line skipped: %(line)s") % {"token": (fields[0], line), "line": self.line_num - 1}) def finish_person(self): """All info has been collected, write to database.""" if self.person is not None: if self.add_name(): self.database.add_person(self.person, self.trans) self.person = None def next_person(self): """A VCard for another person is started.""" if self.person is not None: self.finish_person() self.__add_msg(_("BEGIN property not properly closed by END " "property, Gramps can't cope with nested VCards."), self.line_num - 1) self.person = Person() self.formatted_name = '' self.name_parts = '' def check_version(self, fields, data): """Check the version of the VCard, only version 3.0 is supported.""" self.version = data if self.version != "3.0": raise GrampsImportError(_("Import of VCards version %s is " "not supported by Gramps.") % self.version) def add_formatted_name(self, fields, data): """Read the FN property of a VCard.""" if not self.formatted_name: self.formatted_name = self.unesc(str(data)).strip() def add_name_parts(self, fields, data): """Read the N property of a VCard.""" if not self.name_parts: self.name_parts = data.strip() def add_name(self): """ Add the name to the person. Returns True on success, False on failure. """ if not self.name_parts.strip(): self.__add_msg(_("VCard is malformed missing the compulsory N " "property, so there is no name; skip it."), self.line_num - 1) return False if not self.formatted_name: self.__add_msg(_("VCard is malformed missing the compulsory FN " "property, get name from N alone."), self.line_num - 1) data_fields = self.split_unescaped(self.name_parts, ';') if len(data_fields) != 5: self.__add_msg(_("VCard is malformed wrong number of name " "components."), self.line_num - 1) name = Name() name.set_type(NameType(NameType.BIRTH)) if data_fields[0].strip(): # assume first surname is primary for surname_str in self.split_unescaped(data_fields[0], ','): surname = Surname() prefix, sname = splitof_nameprefix(self.unesc(surname_str)) surname.set_surname(sname.strip()) surname.set_prefix(prefix.strip()) name.add_surname(surname) name.set_primary_surname() if len(data_fields) > 1 and data_fields[1].strip(): given_name = ' '.join(self.unesc( self.split_unescaped(data_fields[1], ','))) else: given_name = '' if len(data_fields) > 2 and data_fields[2].strip(): additional_names = ' '.join(self.unesc( self.split_unescaped(data_fields[2], ','))) else: additional_names = '' self.add_firstname(given_name.strip(), additional_names.strip(), name) if len(data_fields) > 3 and data_fields[3].strip(): name.set_title(' '.join(self.unesc( self.split_unescaped(data_fields[3], ',')))) if len(data_fields) > 4 and data_fields[4].strip(): name.set_suffix(' '.join(self.unesc( self.split_unescaped(data_fields[4], ',')))) self.person.set_primary_name(name) return True def add_firstname(self, given_name, additional_names, name): """ Combine given_name and additional_names and add as firstname to name. If possible try to add given_name as call name. """ default = "%s %s" % (given_name, additional_names) if self.formatted_name: if given_name: if additional_names: given_name_pos = self.formatted_name.find(given_name) if given_name_pos != -1: add_names_pos = self.formatted_name.find(additional_names) if add_names_pos != -1: if given_name_pos <= add_names_pos: firstname = default # Uncertain if given name is used as callname else: firstname = "%s %s" % (additional_names, given_name) name.set_call_name(given_name) else: idx = fitin(self.formatted_name, additional_names, given_name) if idx == -1: # Additional names is not in formatted name firstname = default else: # Given name in middle of additional names firstname = "%s%s %s" % (additional_names[:idx], given_name, additional_names[idx:]) name.set_call_name(given_name) else: # Given name is not in formatted name firstname = default else: # There are no additional_names firstname = given_name else: # There is no given_name firstname = additional_names else: # There is no formatted name firstname = default name.set_first_name(firstname.strip()) return def add_nicknames(self, fields, data): """Read the NICKNAME property of a VCard.""" for nick in self.split_unescaped(data, ','): nickname = nick.strip() if nickname: name = Name() name.set_nick_name(self.unesc(nickname)) self.person.add_alternate_name(name) def add_sortas(self, fields, data): """Read the SORT-STRING property of a VCard.""" # TODO pass def add_address(self, fields, data): """Read the ADR property of a VCard.""" data_fields = self.split_unescaped(data, ';') data_fields = [x.strip() for x in self.unesc(data_fields)] if ''.join(data_fields): addr = Address() def add_street(strng): if strng: already = addr.get_street() if already: addr.set_street("%s %s" % (already, strng)) else: addr.set_street(strng) addr.add_street = add_street set_func = ['add_street', 'add_street', 'add_street', 'set_city', 'set_state', 'set_postal_code', 'set_country'] for i, data in enumerate(data_fields): if i >= len(set_func): break getattr(addr, set_func[i])(data) self.person.add_address(addr) def add_phone(self, fields, data): """Read the TEL property of a VCard.""" tel = data.strip() if tel: addr = Address() addr.set_phone(self.unesc(tel)) self.person.add_address(addr) def add_birthday(self, fields, data): """Read the BDAY property of a VCard.""" date_str = data.strip() date_match = VCardParser.DATE_RE.match(date_str) date = Date() if date_match: if date_match.group(2): date_str = "%s-%s-%s" % (date_match.group(2), date_match.group(3), date_match.group(4)) else: date_str = date_match.group(1) y, m, d = [int(x, 10) for x in date_str.split('-')] try: date.set(value=(d, m, y, False)) except DateError: # TRANSLATORS: leave the {vcard_snippet} untranslated # in the format string, but you may re-order it if needed. self.__add_msg(_( "Invalid date in BDAY {vcard_snippet}, " "preserving date as text." ).format(vcard_snippet=data), self.line_num - 1) date.set(modifier=Date.MOD_TEXTONLY, text=data) else: if date_str: # TRANSLATORS: leave the {vcard_snippet} untranslated. self.__add_msg(_( "Date {vcard_snippet} not in appropriate format " "yyyy-mm-dd, preserving date as text." ).format(vcard_snippet=date_str), self.line_num - 1) date.set(modifier=Date.MOD_TEXTONLY, text=date_str) else: # silently ignore an empty BDAY record return event = Event() event.set_type(EventType(EventType.BIRTH)) event.set_date_object(date) self.database.add_event(event, self.trans) event_ref = EventRef() event_ref.set_reference_handle(event.get_handle()) self.person.set_birth_ref(event_ref) def add_occupation(self, fields, data): """Read the ROLE property of a VCard.""" occupation = data.strip() if occupation: event = Event() event.set_type(EventType(EventType.OCCUPATION)) event.set_description(self.unesc(occupation)) self.database.add_event(event, self.trans) event_ref = EventRef() event_ref.set_reference_handle(event.get_handle()) self.person.add_event_ref(event_ref) def add_url(self, fields, data): """Read the URL property of a VCard.""" href = data.strip() if href: url = Url() url.set_path(self.unesc(href)) self.person.add_url(url) def add_email(self, fields, data): """Read the EMAIL property of a VCard.""" email = data.strip() if email: url = Url() url.set_type(UrlType(UrlType.EMAIL)) url.set_path(self.unesc(email)) self.person.add_url(url) def add_gender(self, fields, data): """Read the GENDER property of a VCard.""" gender_value = data.strip() if gender_value: gender_value = gender_value.upper() gender_value = gender_value[0] if gender_value == 'M': gender = Person.MALE elif gender_value == 'F': gender = Person.FEMALE else: return self.person.set_gender(gender)
class VCardParser: """Class to read data in VCard format from a file.""" DATE_RE = re.compile( r'^(\d{4}-\d{1,2}-\d{1,2})|(?:(\d{4})-?(\d\d)-?(\d\d))') GROUP_RE = re.compile( r'^(?:[-0-9A-Za-z]+\.)?(.+)$') # see RFC 2425 sec5.8.2 ESCAPE_CHAR = '\\' TOBE_ESCAPED = ['\\', ',', ';'] # order is important LINE_CONTINUATION = [' ', '\t'] @staticmethod def name_value_split(data): """Property group.name:value split is on first unquoted colon.""" colon_idx = data.find(':') if colon_idx < 1: return () quote_count = data.count('"', 0, colon_idx) while quote_count % 2 == 1: colon_idx = data.find(':', colon_idx + 1) quote_count = data.count('"', 0, colon_idx) group_name, value = data[:colon_idx], data[colon_idx + 1:] name_parts = VCardParser.GROUP_RE.match(group_name) return (name_parts.group(1), value) @staticmethod def unesc(data): """Remove VCard escape sequences.""" if type(data) == type('string'): for char in reversed(VCardParser.TOBE_ESCAPED): data = data.replace(VCardParser.ESCAPE_CHAR + char, char) return data elif type(data) == type([]): return list(map(VCardParser.unesc, data)) else: raise TypeError("VCard unescaping is not implemented for " "data type %s." % str(type(data))) @staticmethod def count_escapes(strng): """Count the number of escape characters at the end of a string.""" count = 0 for char in reversed(strng): if char != VCardParser.ESCAPE_CHAR: return count count += 1 return count @staticmethod def split_unescaped(strng, sep): """Split on sep if sep is unescaped.""" strng_parts = strng.split(sep) for i in reversed(range(len(strng_parts[:]))): if VCardParser.count_escapes(strng_parts[i]) % 2 == 1: # the sep was escaped so undo split appendix = strng_parts.pop(i + 1) strng_parts[i] += sep + appendix return strng_parts def __init__(self, dbase): self.database = dbase self.formatted_name = '' self.name_parts = '' self.next_line = None self.trans = None self.version = None self.person = None self.errors = [] self.number_of_errors = 0 def __get_next_line(self, filehandle): """ Read and return the line with the next property of the VCard. Also if it spans multiple lines (RFC 2425 sec.5.8.1). """ line = self.next_line self.next_line = filehandle.readline() self.line_num = self.line_num + 1 while self.next_line and self.next_line[0] in self.LINE_CONTINUATION: line = line.rstrip("\n") # TODO perhaps next lines superflous because of rU open parameter? if len(line) > 0 and line[-1] == "\r": line = line[:-1] line += self.next_line[1:] self.next_line = filehandle.readline() self.line_num = self.line_num + 1 if line: line = line.strip() else: line = None return line def __add_msg(self, problem, line=None): if problem != "": self.number_of_errors += 1 if line: message = _("Line %(line)5d: %(prob)s\n") % { "line": line, "prob": problem } else: message = problem + "\n" self.errors.append(message) def parse(self, filehandle, user): """ Prepare the database and parse the input file. :param filehandle: open file handle positioned at start of the file """ tym = time.time() self.person = None self.database.disable_signals() with DbTxn(_("vCard import"), self.database, batch=True) as self.trans: self._parse_vCard_file(filehandle) self.database.enable_signals() self.database.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) if self.number_of_errors == 0: message = _("VCARD import report: No errors detected") else: message = _("VCARD import report: %s errors detected\n") % \ self.number_of_errors if hasattr(user.uistate, 'window'): parent_window = user.uistate.window else: parent_window = None user.info(message, "".join(self.errors), parent=parent_window, monospaced=True) def _parse_vCard_file(self, filehandle): """Read each line of the input file and act accordingly.""" self.next_line = filehandle.readline() self.line_num = 1 while True: line = self.__get_next_line(filehandle) if line is None: break if line == "": continue if line.find(":") == -1: continue line_parts = self.name_value_split(line) if not line_parts: continue # No check for escaped ; because only fields[0] is used. fields = line_parts[0].split(";") property_name = fields[0].upper() if property_name == "BEGIN": self.next_person() elif property_name == "END": self.finish_person() elif property_name == "VERSION": self.check_version(fields, line_parts[1]) elif property_name == "FN": self.add_formatted_name(fields, line_parts[1]) elif property_name == "N": self.add_name_parts(fields, line_parts[1]) elif property_name == "NICKNAME": self.add_nicknames(fields, line_parts[1]) elif property_name == "SORT-STRING": self.add_sortas(fields, line_parts[1]) elif property_name == "ADR": self.add_address(fields, line_parts[1]) elif property_name == "TEL": self.add_phone(fields, line_parts[1]) elif property_name == "BDAY": self.add_birthday(fields, line_parts[1]) elif property_name == "ROLE": self.add_occupation(fields, line_parts[1]) elif property_name == "URL": self.add_url(fields, line_parts[1]) elif property_name == "EMAIL": self.add_email(fields, line_parts[1]) elif property_name == "X-GENDER" or property_name == "GENDER": # VCard 3.0 only has X-GENDER, GENDER is 4.0 syntax, # but we want to be robust here. self.add_gender(fields, line_parts[1]) elif property_name == "PRODID": # Included cause VCards made by Gramps have this prop. pass else: self.__add_msg( _("Token >%(token)s< unknown. line skipped: %(line)s") % { "token": (fields[0], line), "line": self.line_num - 1 }) def finish_person(self): """All info has been collected, write to database.""" if self.person is not None: if self.add_name(): self.database.add_person(self.person, self.trans) self.person = None def next_person(self): """A VCard for another person is started.""" if self.person is not None: self.finish_person() self.__add_msg( _("BEGIN property not properly closed by END " "property, Gramps can't cope with nested VCards."), self.line_num - 1) self.person = Person() self.formatted_name = '' self.name_parts = '' def check_version(self, fields, data): """Check the version of the VCard, only version 3.0 is supported.""" self.version = data if self.version != "3.0": raise GrampsImportError( _("Import of VCards version %s is " "not supported by Gramps.") % self.version) def add_formatted_name(self, fields, data): """Read the FN property of a VCard.""" if not self.formatted_name: self.formatted_name = self.unesc(str(data)).strip() def add_name_parts(self, fields, data): """Read the N property of a VCard.""" if not self.name_parts: self.name_parts = data.strip() def add_name(self): """ Add the name to the person. Returns True on success, False on failure. """ if not self.name_parts.strip(): self.__add_msg( _("VCard is malformed missing the compulsory N " "property, so there is no name; skip it."), self.line_num - 1) return False if not self.formatted_name: self.__add_msg( _("VCard is malformed missing the compulsory FN " "property, get name from N alone."), self.line_num - 1) data_fields = self.split_unescaped(self.name_parts, ';') if len(data_fields) != 5: self.__add_msg( _("VCard is malformed wrong number of name " "components."), self.line_num - 1) name = Name() name.set_type(NameType(NameType.BIRTH)) if data_fields[0].strip(): # assume first surname is primary for surname_str in self.split_unescaped(data_fields[0], ','): surname = Surname() prefix, sname = splitof_nameprefix(self.unesc(surname_str)) surname.set_surname(sname.strip()) surname.set_prefix(prefix.strip()) name.add_surname(surname) name.set_primary_surname() if len(data_fields) > 1 and data_fields[1].strip(): given_name = ' '.join( self.unesc(self.split_unescaped(data_fields[1], ','))) else: given_name = '' if len(data_fields) > 2 and data_fields[2].strip(): additional_names = ' '.join( self.unesc(self.split_unescaped(data_fields[2], ','))) else: additional_names = '' self.add_firstname(given_name.strip(), additional_names.strip(), name) if len(data_fields) > 3 and data_fields[3].strip(): name.set_title(' '.join( self.unesc(self.split_unescaped(data_fields[3], ',')))) if len(data_fields) > 4 and data_fields[4].strip(): name.set_suffix(' '.join( self.unesc(self.split_unescaped(data_fields[4], ',')))) self.person.set_primary_name(name) return True def add_firstname(self, given_name, additional_names, name): """ Combine given_name and additional_names and add as firstname to name. If possible try to add given_name as call name. """ default = "%s %s" % (given_name, additional_names) if self.formatted_name: if given_name: if additional_names: given_name_pos = self.formatted_name.find(given_name) if given_name_pos != -1: add_names_pos = self.formatted_name.find( additional_names) if add_names_pos != -1: if given_name_pos <= add_names_pos: firstname = default # Uncertain if given name is used as callname else: firstname = "%s %s" % (additional_names, given_name) name.set_call_name(given_name) else: idx = fitin(self.formatted_name, additional_names, given_name) if idx == -1: # Additional names is not in formatted name firstname = default else: # Given name in middle of additional names firstname = "%s%s %s" % ( additional_names[:idx], given_name, additional_names[idx:]) name.set_call_name(given_name) else: # Given name is not in formatted name firstname = default else: # There are no additional_names firstname = given_name else: # There is no given_name firstname = additional_names else: # There is no formatted name firstname = default name.set_first_name(firstname.strip()) return def add_nicknames(self, fields, data): """Read the NICKNAME property of a VCard.""" for nick in self.split_unescaped(data, ','): nickname = nick.strip() if nickname: name = Name() name.set_nick_name(self.unesc(nickname)) self.person.add_alternate_name(name) def add_sortas(self, fields, data): """Read the SORT-STRING property of a VCard.""" # TODO pass def add_address(self, fields, data): """Read the ADR property of a VCard.""" data_fields = self.split_unescaped(data, ';') data_fields = [x.strip() for x in self.unesc(data_fields)] if ''.join(data_fields): addr = Address() def add_street(strng): if strng: already = addr.get_street() if already: addr.set_street("%s %s" % (already, strng)) else: addr.set_street(strng) addr.add_street = add_street set_func = [ 'add_street', 'add_street', 'add_street', 'set_city', 'set_state', 'set_postal_code', 'set_country' ] for i, data in enumerate(data_fields): if i >= len(set_func): break getattr(addr, set_func[i])(data) self.person.add_address(addr) def add_phone(self, fields, data): """Read the TEL property of a VCard.""" tel = data.strip() if tel: addr = Address() addr.set_phone(self.unesc(tel)) self.person.add_address(addr) def add_birthday(self, fields, data): """Read the BDAY property of a VCard.""" date_str = data.strip() date_match = VCardParser.DATE_RE.match(date_str) date = Date() if date_match: if date_match.group(2): date_str = "%s-%s-%s" % (date_match.group(2), date_match.group(3), date_match.group(4)) else: date_str = date_match.group(1) y, m, d = [int(x, 10) for x in date_str.split('-')] try: date.set(value=(d, m, y, False)) except DateError: # TRANSLATORS: leave the {vcard_snippet} untranslated # in the format string, but you may re-order it if needed. self.__add_msg( _("Invalid date in BDAY {vcard_snippet}, " "preserving date as text.").format(vcard_snippet=data), self.line_num - 1) date.set(modifier=Date.MOD_TEXTONLY, text=data) else: if date_str: # TRANSLATORS: leave the {vcard_snippet} untranslated. self.__add_msg( _("Date {vcard_snippet} not in appropriate format " "yyyy-mm-dd, preserving date as text.").format( vcard_snippet=date_str), self.line_num - 1) date.set(modifier=Date.MOD_TEXTONLY, text=date_str) else: # silently ignore an empty BDAY record return event = Event() event.set_type(EventType(EventType.BIRTH)) event.set_date_object(date) self.database.add_event(event, self.trans) event_ref = EventRef() event_ref.set_reference_handle(event.get_handle()) self.person.set_birth_ref(event_ref) def add_occupation(self, fields, data): """Read the ROLE property of a VCard.""" occupation = data.strip() if occupation: event = Event() event.set_type(EventType(EventType.OCCUPATION)) event.set_description(self.unesc(occupation)) self.database.add_event(event, self.trans) event_ref = EventRef() event_ref.set_reference_handle(event.get_handle()) self.person.add_event_ref(event_ref) def add_url(self, fields, data): """Read the URL property of a VCard.""" href = data.strip() if href: url = Url() url.set_path(self.unesc(href)) self.person.add_url(url) def add_email(self, fields, data): """Read the EMAIL property of a VCard.""" email = data.strip() if email: url = Url() url.set_type(UrlType(UrlType.EMAIL)) url.set_path(self.unesc(email)) self.person.add_url(url) def add_gender(self, fields, data): """Read the GENDER property of a VCard.""" gender_value = data.strip() if gender_value: gender_value = gender_value.upper() gender_value = gender_value[0] if gender_value == 'M': gender = Person.MALE elif gender_value == 'F': gender = Person.FEMALE else: return self.person.set_gender(gender)
def run_tool(self): self.progress = ProgressMeter(_('Running Date Test'), '', parent=self.parent_window) self.progress.set_pass(_('Generating dates'), 4) dates = [] # first some valid dates calendar = Date.CAL_GREGORIAN for quality in (Date.QUAL_NONE, Date.QUAL_ESTIMATED, Date.QUAL_CALCULATED): for modifier in (Date.MOD_NONE, Date.MOD_BEFORE, Date.MOD_AFTER, Date.MOD_ABOUT): for slash1 in (False, True): for month in range(0, 13): for day in (0, 5, 27): if not month and day: continue d = Date() d.set(quality, modifier, calendar, (day, month, 1789, slash1), "Text comment") dates.append(d) for modifier in (Date.MOD_RANGE, Date.MOD_SPAN): for slash1 in (False, True): for slash2 in (False, True): for month in range(0, 13): for day in (0, 5, 27): if not month and day: continue d = Date() d.set(quality, modifier, calendar, (day, month, 1789, slash1, day, month, 1876, slash2), "Text comment") dates.append(d) if not month: continue d = Date() d.set(quality, modifier, calendar, (day, month, 1789, slash1, day, 13 - month, 1876, slash2), "Text comment") dates.append(d) if not day: continue d = Date() d.set(quality, modifier, calendar, (day, month, 1789, slash1, 32 - day, month, 1876, slash2), "Text comment") dates.append(d) d = Date() d.set(quality, modifier, calendar, (day, month, 1789, slash1, 32 - day, 13 - month, 1876, slash2), "Text comment") dates.append(d) modifier = Date.MOD_TEXTONLY d = Date() d.set(quality, modifier, calendar, Date.EMPTY, "This is a textual date") dates.append(d) self.progress.step() # test invalid dates #dateval = (4,7,1789,False,5,8,1876,False) #for l in range(1,len(dateval)): # d = Date() # try: # d.set(Date.QUAL_NONE,Date.MOD_NONE, # Date.CAL_GREGORIAN,dateval[:l],"Text comment") # dates.append( d) # except DateError, e: # d.set_as_text("Date identified value correctly as invalid.\n%s" % e) # dates.append( d) # except: # d = Date() # d.set_as_text("Date.set Exception %s" % ("".join(traceback.format_exception(*sys.exc_info())),)) # dates.append( d) #for l in range(1,len(dateval)): # d = Date() # try: # d.set(Date.QUAL_NONE,Date.MOD_SPAN,Date.CAL_GREGORIAN,dateval[:l],"Text comment") # dates.append( d) # except DateError, e: # d.set_as_text("Date identified value correctly as invalid.\n%s" % e) # dates.append( d) # except: # d = Date() # d.set_as_text("Date.set Exception %s" % ("".join(traceback.format_exception(*sys.exc_info())),)) # dates.append( d) #self.progress.step() #d = Date() #d.set(Date.QUAL_NONE,Date.MOD_NONE, # Date.CAL_GREGORIAN,(44,7,1789,False),"Text comment") #dates.append( d) #d = Date() #d.set(Date.QUAL_NONE,Date.MOD_NONE, # Date.CAL_GREGORIAN,(4,77,1789,False),"Text comment") #dates.append( d) #d = Date() #d.set(Date.QUAL_NONE,Date.MOD_SPAN, # Date.CAL_GREGORIAN, # (4,7,1789,False,55,8,1876,False),"Text comment") #dates.append( d) #d = Date() #d.set(Date.QUAL_NONE,Date.MOD_SPAN, # Date.CAL_GREGORIAN, # (4,7,1789,False,5,88,1876,False),"Text comment") #dates.append( d) with DbTxn(_("Date Test Plugin"), self.db, batch=True) as self.trans: self.db.disable_signals() self.progress.set_pass(_('Generating dates'), len(dates)) # create pass and fail tags pass_handle = self.create_tag(_('Pass'), '#0000FFFF0000') fail_handle = self.create_tag(_('Fail'), '#FFFF00000000') # now add them as birth to new persons i = 1 for dateval in dates: person = Person() surname = Surname() surname.set_surname("DateTest") name = Name() name.add_surname(surname) name.set_first_name("Test %d" % i) person.set_primary_name(name) self.db.add_person(person, self.trans) bevent = Event() bevent.set_type(EventType.BIRTH) bevent.set_date_object(dateval) bevent.set_description("Date Test %d (source)" % i) bevent_h = self.db.add_event(bevent, self.trans) bevent_ref = EventRef() bevent_ref.set_reference_handle(bevent_h) # for the death event display the date as text and parse it back to a new date ndate = None try: datestr = _dd.display(dateval) try: ndate = _dp.parse(datestr) if not ndate: ndate = Date() ndate.set_as_text("DateParser None") person.add_tag(fail_handle) else: person.add_tag(pass_handle) except: ndate = Date() ndate.set_as_text("DateParser Exception %s" % ("".join( traceback.format_exception(*sys.exc_info())), )) person.add_tag(fail_handle) else: person.add_tag(pass_handle) except: ndate = Date() ndate.set_as_text("DateDisplay Exception: %s" % ("".join( traceback.format_exception(*sys.exc_info())), )) person.add_tag(fail_handle) if dateval.get_modifier() != Date.MOD_TEXTONLY \ and ndate.get_modifier() == Date.MOD_TEXTONLY: # parser was unable to correctly parse the string ndate.set_as_text("TEXTONLY: " + ndate.get_text()) person.add_tag(fail_handle) if dateval.get_modifier() == Date.MOD_TEXTONLY \ and dateval.get_text().count("Traceback") \ and pass_handle in person.get_tag_list(): person.add_tag(fail_handle) devent = Event() devent.set_type(EventType.DEATH) devent.set_date_object(ndate) devent.set_description("Date Test %d (result)" % i) devent_h = self.db.add_event(devent, self.trans) devent_ref = EventRef() devent_ref.set_reference_handle(devent_h) person.set_birth_ref(bevent_ref) person.set_death_ref(devent_ref) self.db.commit_person(person, self.trans) i = i + 1 self.progress.step() self.db.enable_signals() self.db.request_rebuild() self.progress.close()