Source code for gramps.gen.display.name

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2004-2007  Donald N. Allingham
# Copyright (C) 2010       Brian G. Matherly
# Copyright (C) 2014       Paul Franklin
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

"""
Class handling language-specific displaying of names.

Specific symbols for parts of a name are defined:

    ======  ===============================================================
    Symbol  Description
    ======  ===============================================================
    't'     title
    'f'     given (first names)
    'l'     full surname (lastname)
    'c'     callname
    'x'     nick name if existing, otherwise first first name (common name)
    'i'     initials of the first names
    'm'     primary surname (main)
    '0m'    primary surname prefix
    '1m'    primary surname surname
    '2m'    primary surname connector
    'y'     pa/matronymic surname (father/mother) - assumed unique
    '0y'    pa/matronymic prefix
    '1y'    pa/matronymic surname
    '2y'    pa/matronymic connector
    'o'     surnames without pa/matronymic and primary
    'r'     non primary surnames (rest)
    'p'     list of all prefixes
    'q'     surnames without prefixes and connectors
    's'     suffix
    'n'     nick name
    'g'     family nick name
    ======  ===============================================================
"""

#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import re
import logging
LOG = logging.getLogger(".gramps.gen")

#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from ..const import ARABIC_COMMA, ARABIC_SEMICOLON, GRAMPS_LOCALE as glocale
_ = glocale.translation.sgettext
from ..constfunc import cuni, conv_to_unicode, UNITYPE
from ..lib.name import Name
from ..lib.nameorigintype import NameOriginType

try:
    from ..config import config
    WITH_GRAMPS_CONFIG=True
except ImportError:
    WITH_GRAMPS_CONFIG=False
    

#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
_FIRSTNAME    = 4
_SURNAME_LIST = 5
_SUFFIX       = 6
_TITLE        = 7
_TYPE         = 8
_GROUP        = 9
_SORT         = 10
_DISPLAY      = 11
_CALL         = 12
_NICK         = 13
_FAMNICK      = 14
_SURNAME_IN_LIST   = 0
_PREFIX_IN_LIST    = 1
_PRIMARY_IN_LIST   = 2
_TYPE_IN_LIST      = 3
_CONNECTOR_IN_LIST = 4
_ORIGINPATRO = NameOriginType.PATRONYMIC
_ORIGINMATRO = NameOriginType.MATRONYMIC

_ACT = True
_INA = False

_F_NAME = 0  # name of the format
_F_FMT = 1   # the format string
_F_ACT = 2   # if the format is active
_F_FN = 3    # name format function
_F_RAWFN = 4 # name format raw function

PAT_AS_SURN = False

#-------------------------------------------------------------------------
#
# Local functions
#
#-------------------------------------------------------------------------
# Because of occurring in an exec(), this couldn't be in a lambda:
# we sort names first on longest first, then last letter first, this to 
# avoid translations of shorter terms which appear in longer ones, eg
# namelast may not be mistaken with name, so namelast must first be 
# converted to %k before name is converted. 
##def _make_cmp(a, b): return -cmp((len(a[1]),a[1]), (len(b[1]), b[1]))
def _make_cmp_key(a): return (len(a[1]),a[1])  # set reverse to True!!

#-------------------------------------------------------------------------
#
# NameDisplayError class
#
#-------------------------------------------------------------------------
[docs]class NameDisplayError(Exception): """ Error used to report that the name display format string is invalid. """ def __init__(self, value): Exception.__init__(self) self.value = value def __str__(self): return self.value #------------------------------------------------------------------------- # # Functions to extract data from raw lists (unserialized objects) # #-------------------------------------------------------------------------
def _raw_full_surname(raw_surn_data_list): """method for the 'l' symbol: full surnames""" result = "" for raw_surn_data in raw_surn_data_list: result += "%s %s %s " % (raw_surn_data[_PREFIX_IN_LIST], raw_surn_data[_SURNAME_IN_LIST], raw_surn_data[_CONNECTOR_IN_LIST]) return ' '.join(result.split()).strip() def _raw_primary_surname(raw_surn_data_list): """method for the 'm' symbol: primary surname""" global PAT_AS_SURN nrsur = len(raw_surn_data_list) for raw_surn_data in raw_surn_data_list: if raw_surn_data[_PRIMARY_IN_LIST]: #if there are multiple surnames, return the primary. If there #is only one surname, then primary has little meaning, and we #assume a pa/matronymic should not be given as primary as it #normally is defined independently if not PAT_AS_SURN and nrsur == 1 and \ (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): return '' else: result = "%s %s %s" % (raw_surn_data[_PREFIX_IN_LIST], raw_surn_data[_SURNAME_IN_LIST], raw_surn_data[_CONNECTOR_IN_LIST]) return ' '.join(result.split()) return '' def _raw_primary_surname_only(raw_surn_data_list): """method to obtain the raw primary surname data, so this returns a string """ global PAT_AS_SURN nrsur = len(raw_surn_data_list) for raw_surn_data in raw_surn_data_list: if raw_surn_data[_PRIMARY_IN_LIST]: if not PAT_AS_SURN and nrsur == 1 and \ (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): return '' else: return raw_surn_data[_SURNAME_IN_LIST] return '' def _raw_primary_prefix_only(raw_surn_data_list): """method to obtain the raw primary surname data""" global PAT_AS_SURN nrsur = len(raw_surn_data_list) for raw_surn_data in raw_surn_data_list: if raw_surn_data[_PRIMARY_IN_LIST]: if not PAT_AS_SURN and nrsur == 1 and \ (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): return '' else: return raw_surn_data[_PREFIX_IN_LIST] return '' def _raw_primary_conn_only(raw_surn_data_list): """method to obtain the raw primary surname data""" global PAT_AS_SURN nrsur = len(raw_surn_data_list) for raw_surn_data in raw_surn_data_list: if raw_surn_data[_PRIMARY_IN_LIST]: if not PAT_AS_SURN and nrsur == 1 and \ (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): return '' else: return raw_surn_data[_CONNECTOR_IN_LIST] return '' def _raw_patro_surname(raw_surn_data_list): """method for the 'y' symbol: patronymic surname""" for raw_surn_data in raw_surn_data_list: if (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): result = "%s %s %s" % (raw_surn_data[_PREFIX_IN_LIST], raw_surn_data[_SURNAME_IN_LIST], raw_surn_data[_CONNECTOR_IN_LIST]) return ' '.join(result.split()) return '' def _raw_patro_surname_only(raw_surn_data_list): """method for the '1y' symbol: patronymic surname only""" for raw_surn_data in raw_surn_data_list: if (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): result = "%s" % (raw_surn_data[_SURNAME_IN_LIST]) return ' '.join(result.split()) return '' def _raw_patro_prefix_only(raw_surn_data_list): """method for the '0y' symbol: patronymic prefix only""" for raw_surn_data in raw_surn_data_list: if (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): result = "%s" % (raw_surn_data[_PREFIX_IN_LIST]) return ' '.join(result.split()) return '' def _raw_patro_conn_only(raw_surn_data_list): """method for the '2y' symbol: patronymic conn only""" for raw_surn_data in raw_surn_data_list: if (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): result = "%s" % (raw_surn_data[_CONNECTOR_IN_LIST]) return ' '.join(result.split()) return '' def _raw_nonpatro_surname(raw_surn_data_list): """method for the 'o' symbol: full surnames without pa/matronymic or primary """ result = "" for raw_surn_data in raw_surn_data_list: if ((not raw_surn_data[_PRIMARY_IN_LIST]) and raw_surn_data[_TYPE_IN_LIST][0] != _ORIGINPATRO and raw_surn_data[_TYPE_IN_LIST][0] != _ORIGINMATRO): result += "%s %s %s " % (raw_surn_data[_PREFIX_IN_LIST], raw_surn_data[_SURNAME_IN_LIST], raw_surn_data[_CONNECTOR_IN_LIST]) return ' '.join(result.split()).strip() def _raw_nonprimary_surname(raw_surn_data_list): """method for the 'r' symbol: nonprimary surnames""" result = '' for raw_surn_data in raw_surn_data_list: if not raw_surn_data[_PRIMARY_IN_LIST]: result = "%s %s %s %s" % (result, raw_surn_data[_PREFIX_IN_LIST], raw_surn_data[_SURNAME_IN_LIST], raw_surn_data[_CONNECTOR_IN_LIST]) return ' '.join(result.split()) def _raw_prefix_surname(raw_surn_data_list): """method for the 'p' symbol: all prefixes""" result = "" for raw_surn_data in raw_surn_data_list: result += "%s " % (raw_surn_data[_PREFIX_IN_LIST]) return ' '.join(result.split()).strip() def _raw_single_surname(raw_surn_data_list): """method for the 'q' symbol: surnames without prefix and connectors""" result = "" for raw_surn_data in raw_surn_data_list: result += "%s " % (raw_surn_data[_SURNAME_IN_LIST]) return ' '.join(result.split()).strip()
[docs]def cleanup_name(namestring): """Remove too long white space due to missing name parts, so "a b" becomes "a b" and "a , b" becomes "a, b" """ parts = namestring.split() if not parts: return "" result = parts[0] for val in parts[1:]: if len(val) == 1 and val in [',', ';', ':', ARABIC_COMMA, ARABIC_SEMICOLON]: result += val else: result += ' ' + val return result #------------------------------------------------------------------------- # # NameDisplay class # #-------------------------------------------------------------------------
[docs]class NameDisplay(object): """ Base class for displaying of Name instances. Property: *default_format* the default name format to use *pas_as_surn* if only one surname, see if pa/ma should be considered as 'the' surname. """ format_funcs = {} raw_format_funcs = {} def __init__(self, xlocale=glocale): """ Initialize the NameDisplay class. If xlocale is passed in (a GrampsLocale), then the translated script will be returned instead. :param xlocale: allow selection of the displayer script :type xlocale: a GrampsLocale instance """ global WITH_GRAMPS_CONFIG global PAT_AS_SURN # translators: needed for Arabic, ignore otherwise COMMAGLYPH = xlocale.translation.gettext(',') self.STANDARD_FORMATS = [ (Name.DEF, _("Default format (defined by Gramps preferences)"), '', _ACT), (Name.LNFN, _("Surname, Given Suffix"), '%l' + COMMAGLYPH + ' %f %s', _ACT), (Name.FN, _("Given"), '%f', _ACT), (Name.FNLN, _("Given Surname Suffix"), '%f %l %s', _ACT), # primary name primconnector other, given pa/matronynic suffix, primprefix # translators: long string, have a look at Preferences dialog (Name.LNFNP, _("Main Surnames, Given Patronymic Suffix Prefix"), '%1m %2m %o' + COMMAGLYPH + ' %f %1y %s %0m', _ACT), # DEPRECATED FORMATS (Name.PTFN, _("Patronymic, Given"), '%y' + COMMAGLYPH + ' %s %f', _INA), ] self.LNFN_STR = "%s" + COMMAGLYPH + " %s %s" self.name_formats = {} if WITH_GRAMPS_CONFIG: self.default_format = config.get('preferences.name-format') if self.default_format == 0: self.default_format = Name.LNFN config.set('preferences.name-format', self.default_format) #if only one surname, see if pa/ma should be considered as # 'the' surname. PAT_AS_SURN = config.get('preferences.patronimic-surname') config.connect('preferences.patronimic-surname', self.change_pa_sur) else: self.default_format = Name.LNFN PAT_AS_SURN = False #preinit the name formats, this should be updated with the data #in the database once a database is loaded self.set_name_format(self.STANDARD_FORMATS)
[docs] def change_pa_sur(self, *args): """ How to handle single patronymic as surname is changed""" global PAT_AS_SURN PAT_AS_SURN = config.get('preferences.patronimic-surname')
[docs] def get_pat_as_surn(self): global PAT_AS_SURN return PAT_AS_SURN
def _format_fn(self, fmt_str): return lambda x: self.format_str(x, fmt_str) def _format_raw_fn(self, fmt_str): return lambda x: self.format_str_raw(x, fmt_str) def _raw_lnfn(self, raw_data): result = self.LNFN_STR % (_raw_full_surname(raw_data[_SURNAME_LIST]), raw_data[_FIRSTNAME], raw_data[_SUFFIX]) return ' '.join(result.split()) def _raw_fnln(self, raw_data): result = "%s %s %s" % (raw_data[_FIRSTNAME], _raw_full_surname(raw_data[_SURNAME_LIST]), raw_data[_SUFFIX]) return ' '.join(result.split()) def _raw_fn(self, raw_data): result = raw_data[_FIRSTNAME] return ' '.join(result.split())
[docs] def set_name_format(self, formats): raw_func_dict = { Name.LNFN : self._raw_lnfn, Name.FNLN : self._raw_fnln, Name.FN : self._raw_fn, } for (num, name, fmt_str, act) in formats: func = self._format_fn(fmt_str) func_raw = raw_func_dict.get(num, self._format_raw_fn(fmt_str)) self.name_formats[num] = (name, fmt_str, act, func, func_raw) self.set_default_format(self.get_default_format())
[docs] def add_name_format(self, name, fmt_str): num = -1 while num in self.name_formats: num -= 1 self.set_name_format([(num, name, fmt_str,_ACT)]) return num
[docs] def edit_name_format(self, num, name, fmt_str): self.set_name_format([(num, name, fmt_str,_ACT)]) if self.default_format == num: self.set_default_format(num)
[docs] def del_name_format(self, num): try: del self.name_formats[num] except: pass
[docs] def set_default_format(self, num): if num not in self.name_formats: num = Name.LNFN # if user sets default format to the Gramps default format, # then we select LNFN as format. if num == Name.DEF: num = Name.LNFN self.default_format = num self.name_formats[Name.DEF] = (self.name_formats[Name.DEF][_F_NAME], self.name_formats[Name.DEF][_F_FMT], self.name_formats[Name.DEF][_F_ACT], self.name_formats[num][_F_FN], self.name_formats[num][_F_RAWFN])
[docs] def get_default_format(self): return self.default_format
[docs] def set_format_inactive(self, num): try: self.name_formats[num] = (self.name_formats[num][_F_NAME], self.name_formats[num][_F_FMT], _INA, self.name_formats[num][_F_FN], self.name_formats[num][_F_RAWFN]) except: pass
[docs] def get_name_format(self, also_default=False, only_custom=False, only_active=True): """ Get a list of tuples (num, name,fmt_str,act) """ the_list = [] keys = sorted(self.name_formats, key=self.cmp_to_key(self._sort_name_format)) for num in keys: if ((also_default or num) and (not only_custom or (num < 0)) and (not only_active or self.name_formats[num][_F_ACT])): the_list.append((num,) + self.name_formats[num][_F_NAME:_F_FN]) return the_list
[docs] def cmp_to_key(self, mycmp): """ python 2 to 3 conversion, python recipe http://code.activestate.com/recipes/576653/ Convert a :func:`cmp` function into a :func:`key` function We use this in Gramps as understanding the old compare function is not trivial. This should be replaced by a proper key function """ class K(object): def __init__(self, obj, *args): self.obj = obj def __lt__(self, other): return mycmp(self.obj, other.obj) < 0 def __gt__(self, other): return mycmp(self.obj, other.obj) > 0 def __eq__(self, other): return mycmp(self.obj, other.obj) == 0 def __le__(self, other): return mycmp(self.obj, other.obj) <= 0 def __ge__(self, other): return mycmp(self.obj, other.obj) >= 0 def __ne__(self, other): return mycmp(self.obj, other.obj) != 0 return K
def _sort_name_format(self, x, y): if x < 0: if y < 0: return x+y else: return -x+y else: if y < 0: return -x+y else: return x-y def _is_format_valid(self, num): try: if not self.name_formats[num][_F_ACT]: num = 0 except: num = 0 return num #------------------------------------------------------------------------- def _gen_raw_func(self, format_str): """The job of building the name from a format string is rather expensive and it is called lots and lots of times. So it is worth going to some length to optimise it as much as possible. This method constructs a new function that is specifically written to format a name given a particular format string. This is worthwhile because the format string itself rarely changes, so by caching the new function and calling it directly when asked to format a name to the same format string again we can be as quick as possible. The new function is of the form:: def fn(raw_data): return "%s %s %s" % (raw_data[_TITLE], raw_data[_FIRSTNAME], raw_data[_SUFFIX]) Specific symbols for parts of a name are defined (keywords given): 't' : title = title 'f' : given = given (first names) 'l' : surname = full surname (lastname) 'c' : call = callname 'x' : common = nick name if existing, otherwise first first name (common name) 'i' : initials = initials of the first names 'm' : primary = primary surname (main) '0m': primary[pre]= prefix primary surname (main) '1m': primary[sur]= surname primary surname (main) '2m': primary[con]= connector primary surname (main) 'y' : patronymic = pa/matronymic surname (father/mother) - assumed unique '0y': patronymic[pre] = prefix " '1y': patronymic[sur] = surname " '2y': patronymic[con] = connector " 'o' : notpatronymic = surnames without pa/matronymic and primary 'r' : rest = non primary surnames 'p' : prefix = list of all prefixes 'q' : rawsurnames = surnames without prefixes and connectors 's' : suffix = suffix 'n' : nickname = nick name 'g' : familynick = family nick name """ # we need the names of each of the variables or methods that are # called to fill in each format flag. # Dictionary is "code": ("expression", "keyword", "i18n-keyword") d = {"t": ("raw_data[_TITLE]", "title", _("Person|title")), "f": ("raw_data[_FIRSTNAME]", "given", _("given")), "l": ("_raw_full_surname(raw_data[_SURNAME_LIST])", "surname", _("surname")), "s": ("raw_data[_SUFFIX]", "suffix", _("suffix")), "c": ("raw_data[_CALL]", "call", _("Name|call")), "x": ("(raw_data[_NICK] or raw_data[_FIRSTNAME].split(' ')[0])", "common", _("Name|common")), "i": ("''.join([word[0] +'.' for word in ('. ' +" + " raw_data[_FIRSTNAME]).split()][1:])", "initials", _("initials")), "m": ("_raw_primary_surname(raw_data[_SURNAME_LIST])", "primary", _("Name|primary")), "0m": ("_raw_primary_prefix_only(raw_data[_SURNAME_LIST])", "primary[pre]", _("primary[pre]")), "1m": ("_raw_primary_surname_only(raw_data[_SURNAME_LIST])", "primary[sur]", _("primary[sur]")), "2m": ("_raw_primary_conn_only(raw_data[_SURNAME_LIST])", "primary[con]", _("primary[con]")), "y": ("_raw_patro_surname(raw_data[_SURNAME_LIST])", "patronymic", _("patronymic")), "0y": ("_raw_patro_prefix_only(raw_data[_SURNAME_LIST])", "patronymic[pre]", _("patronymic[pre]")), "1y": ("_raw_patro_surname_only(raw_data[_SURNAME_LIST])", "patronymic[sur]", _("patronymic[sur]")), "2y": ("_raw_patro_conn_only(raw_data[_SURNAME_LIST])", "patronymic[con]", _("patronymic[con]")), "o": ("_raw_nonpatro_surname(raw_data[_SURNAME_LIST])", "notpatronymic", _("notpatronymic")), "r": ("_raw_nonprimary_surname(raw_data[_SURNAME_LIST])", "rest", _("Remaining names|rest")), "p": ("_raw_prefix_surname(raw_data[_SURNAME_LIST])", "prefix", _("prefix")), "q": ("_raw_single_surname(raw_data[_SURNAME_LIST])", "rawsurnames", _("rawsurnames")), "n": ("raw_data[_NICK]", "nickname", _("nickname")), "g": ("raw_data[_FAMNICK]", "familynick", _("familynick")), } args = "raw_data" return self._make_fn(format_str, d, args) def _gen_cooked_func(self, format_str): """The job of building the name from a format string is rather expensive and it is called lots and lots of times. So it is worth going to some length to optimise it as much as possible. This method constructs a new function that is specifically written to format a name given a particular format string. This is worthwhile because the format string itself rarely changes, so by caching the new function and calling it directly when asked to format a name to the same format string again we can be as quick as possible. The new function is of the form:: def fn(first, raw_surname_list, suffix, title, call,): return "%s %s" % (first,suffix) Specific symbols for parts of a name are defined (keywords given): 't' : title = title 'f' : given = given (first names) 'l' : surname = full surname (lastname) 'c' : call = callname 'x' : common = nick name if existing, otherwise first first name (common name) 'i' : initials = initials of the first names 'm' : primary = primary surname (main) '0m': primary[pre]= prefix primary surname (main) '1m': primary[sur]= surname primary surname (main) '2m': primary[con]= connector primary surname (main) 'y' : patronymic = pa/matronymic surname (father/mother) - assumed unique '0y': patronymic[pre] = prefix " '1y': patronymic[sur] = surname " '2y': patronymic[con] = connector " 'o' : notpatronymic = surnames without pa/matronymic and primary 'r' : rest = non primary surnames 'p' : prefix = list of all prefixes 'q' : rawsurnames = surnames without prefixes and connectors 's' : suffix = suffix 'n' : nickname = nick name 'g' : familynick = family nick name """ # we need the names of each of the variables or methods that are # called to fill in each format flag. # Dictionary is "code": ("expression", "keyword", "i18n-keyword") d = {"t": ("title", "title", _("Person|title")), "f": ("first", "given", _("given")), "l": ("_raw_full_surname(raw_surname_list)", "surname", _("surname")), "s": ("suffix", "suffix", _("suffix")), "c": ("call", "call", _("Name|call")), "x": ("(nick or first.split(' ')[0])", "common", _("Name|common")), "i": ("''.join([word[0] +'.' for word in ('. ' + first).split()][1:])", "initials", _("initials")), "m": ("_raw_primary_surname(raw_surname_list)", "primary", _("Name|primary")), "0m":("_raw_primary_prefix_only(raw_surname_list)", "primary[pre]", _("primary[pre]")), "1m":("_raw_primary_surname_only(raw_surname_list)", "primary[sur]",_("primary[sur]")), "2m":("_raw_primary_conn_only(raw_surname_list)", "primary[con]", _("primary[con]")), "y": ("_raw_patro_surname(raw_surname_list)", "patronymic", _("patronymic")), "0y":("_raw_patro_prefix_only(raw_surname_list)", "patronymic[pre]", _("patronymic[pre]")), "1y":("_raw_patro_surname_only(raw_surname_list)", "patronymic[sur]", _("patronymic[sur]")), "2y":("_raw_patro_conn_only(raw_surname_list)", "patronymic[con]", _("patronymic[con]")), "o": ("_raw_nonpatro_surname(raw_surname_list)", "notpatronymic", _("notpatronymic")), "r": ("_raw_nonprimary_surname(raw_surname_list)", "rest", _("Remaining names|rest")), "p": ("_raw_prefix_surname(raw_surname_list)", "prefix", _("prefix")), "q": ("_raw_single_surname(raw_surname_list)", "rawsurnames", _("rawsurnames")), "n": ("nick", "nickname", _("nickname")), "g": ("famnick", "familynick", _("familynick")), } args = "first,raw_surname_list,suffix,title,call,nick,famnick" return self._make_fn(format_str, d, args)
[docs] def format_str(self, name, format_str): return self._format_str_base(name.first_name, name.surname_list, name.suffix, name.title, name.call, name.nick, name.famnick, format_str)
[docs] def format_str_raw(self, raw_data, format_str): """ Format a name from the raw name list. To make this as fast as possible this uses :func:`_gen_raw_func` to generate a new method for each new format_string. Is does not call :meth:`_format_str_base` because it would introduce an extra method call and we need all the speed we can squeeze out of this. """ func = self.__class__.raw_format_funcs.get(format_str) if func is None: func = self._gen_raw_func(format_str) self.__class__.raw_format_funcs[format_str] = func return func(raw_data)
def _format_str_base(self, first, surname_list, suffix, title, call, nick, famnick, format_str): """ Generates name from a format string. The following substitutions are made: '%t' : title '%f' : given (first names) '%l' : full surname (lastname) '%c' : callname '%x' : nick name if existing, otherwise first first name (common name) '%i' : initials of the first names '%m' : primary surname (main) '%0m': prefix primary surname (main) '%1m': surname primary surname (main) '%2m': connector primary surname (main) '%y' : pa/matronymic surname (father/mother) - assumed unique '%0y': prefix " '%1y': surname " '%2y': connector " '%o' : surnames without patronymic '%r' : non-primary surnames (rest) '%p' : list of all prefixes '%q' : surnames without prefixes and connectors '%s' : suffix '%n' : nick name '%g' : family nick name The capital letters are substituted for capitalized name components. The %% is substituted with the single % character. All the other characters in the fmt_str are unaffected. """ func = self.__class__.format_funcs.get(format_str) if func is None: func = self._gen_cooked_func(format_str) self.__class__.format_funcs[format_str] = func try: s = func(first, [surn.serialize() for surn in surname_list], suffix, title, call, nick, famnick) except (ValueError, TypeError,): raise NameDisplayError("Incomplete format string") return s #-------------------------------------------------------------------------
[docs] def primary_surname(self, name): global PAT_AS_SURN nrsur = len(name.surname_list) sur = name.get_primary_surname() if not PAT_AS_SURN and nrsur <= 1 and \ (sur.get_origintype().value == _ORIGINPATRO or sur.get_origintype().value == _ORIGINMATRO): return '' return sur.get_surname()
[docs] def sort_string(self, name): return cuni("%-25s%-30s%s") % (self.primary_surname(name), name.first_name, name.suffix)
[docs] def sorted(self, person): """ Return a text string representing the :class:`~.person.Person` instance's :class:`~.name.Name` in a manner that should be used for displaying a sortedname. :param person: :class:`~.person.Person` instance that contains the :class:`~.name.Name` that is to be displayed. The primary name is used for the display. :type person: :class:`~.person.Person` :returns: Returns the :class:`~.person.Person` instance's name :rtype: str """ name = person.get_primary_name() return self.sorted_name(name)
[docs] def sorted_name(self, name): """ Return a text string representing the :class:`~.name.Name` instance in a manner that should be used for sorting the name in a list. :param name: :class:`~.name.Name` instance that is to be displayed. :type name: :class:`~.name.Name` :returns: Returns the :class:`~.name.Name` string representation :rtype: str """ num = self._is_format_valid(name.sort_as) return self.name_formats[num][_F_FN](name)
[docs] def truncate(self, full_name, max_length=15, elipsis="..."): name_out = "" if len(full_name) <= max_length: name_out = full_name else: last_space = full_name.rfind(" ", max_length) if (last_space) > -1: name_out = full_name[:last_space] else: name_out = full_name[:max_length] name_out += " " + elipsis return name_out
[docs] def raw_sorted_name(self, raw_data): """ Return a text string representing the :class:`~.name.Name` instance in a manner that should be used for sorting the name in a list. :param name: raw unserialized data of name that is to be displayed. :type name: tuple :returns: Returns the :class:`~.name.Name` string representation :rtype: str """ num = self._is_format_valid(raw_data[_SORT]) return self.name_formats[num][_F_RAWFN](raw_data)
[docs] def display(self, person): """ Return a text string representing the :class:`~.person.Person` instance's :class:`~.name.Name` in a manner that should be used for normal displaying. :param person: :class:`~.person.Person` instance that contains the :class:`~.name.Name` that is to be displayed. The primary name is used for the display. :type person: :class:`~.person.Person` :returns: Returns the :class:`~.person.Person` instance's name :rtype: str """ name = person.get_primary_name() return self.display_name(name)
[docs] def display_formal(self, person): """ Return a text string representing the :class:`~.person.Person` instance's :class:`~.name.Name` in a manner that should be used for formal displaying. :param person: :class:`~.person.Person` instance that contains the :class:`~.name.Name` that is to be displayed. The primary name is used for the display. :type person: :class:`~.person.Person` :returns: Returns the :class:`~.person.Person` instance's name :rtype: str """ # FIXME: At this time, this is just duplicating display() method name = person.get_primary_name() return self.display_name(name)
[docs] def display_name(self, name): """ Return a text string representing the :class:`~.name.Name` instance in a manner that should be used for normal displaying. :param name: :class:`~.name.Name` instance that is to be displayed. :type name: :class:`~.name.Name` :returns: Returns the :class:`~.name.Name` string representation :rtype: str """ if name is None: return "" num = self._is_format_valid(name.display_as) return self.name_formats[num][_F_FN](name)
[docs] def raw_display_name(self, raw_data): """ Return a text string representing the :class:`~.name.Name` instance in a manner that should be used for normal displaying. :param name: raw unserialized data of name that is to be displayed. :type name: tuple :returns: Returns the :class:`~.name.Name` string representation :rtype: str """ num = self._is_format_valid(raw_data[_DISPLAY]) return self.name_formats[num][_F_RAWFN](raw_data)
[docs] def display_given(self, person): return self.format_str(person.get_primary_name(),'%f')
[docs] def name_grouping(self, db, person): """ Return the name under which to group this person. This is defined as: 1. if group name is defined on primary name, use that 2. if group name is defined for the primary surname of the primary name, use that 3. use primary surname of primary name otherwise """ return self.name_grouping_name(db, person.primary_name)
[docs] def name_grouping_name(self, db, pn): """ Return the name under which to group. This is defined as: 1. if group name is defined, use that 2. if group name is defined for the primary surname, use that 3. use primary surname itself otherwise :param pn: :class:`~.name.Name` object :type pn: :class:`~.name.Name` instance :returns: Returns the groupname string representation :rtype: str """ if pn.group_as: return pn.group_as return db.get_name_group_mapping(pn.get_primary_surname().get_surname())
[docs] def name_grouping_data(self, db, pn): """ Return the name under which to group. This is defined as: 1. if group name is defined, use that 2. if group name is defined for the primary surname, use that 3. use primary surname itself otherwise :param pn: raw unserialized data of name :type pn: tuple :returns: Returns the groupname string representation :rtype: str """ if pn[_GROUP]: return pn[_GROUP] return db.get_name_group_mapping(_raw_primary_surname_only( pn[_SURNAME_LIST]))
def _make_fn(self, format_str, d, args): """ Create the name display function and handles dependent punctuation. """ # d is a dict: dict[code] = (expr, word, translated word) # First, go through and do internationalization-based # key-word replacement. Just replace ikeywords with # %codes (ie, replace "irstnamefay" with "%f", and # "IRSTNAMEFAY" for %F) if (len(format_str) > 2 and format_str[0] == format_str[-1] == '"'): pass else: d_keys = [(code, _tuple[2]) for code, _tuple in d.items()] d_keys.sort(key=_make_cmp_key, reverse=True) # reverse on length and by ikeyword for (code, ikeyword) in d_keys: exp, keyword, ikeyword = d[code] #ikeyword = unicode(ikeyword, "utf8") format_str = format_str.replace(ikeyword, "%"+ code) format_str = format_str.replace(ikeyword.title(), "%"+ code) format_str = format_str.replace(ikeyword.upper(), "%"+ code.upper()) # Next, go through and do key-word replacement. # Just replace keywords with # %codes (ie, replace "firstname" with "%f", and # "FIRSTNAME" for %F) if (len(format_str) > 2 and format_str[0] == format_str[-1] == '"'): pass else: d_keys = [(code, _tuple[1]) for code, _tuple in d.items()] d_keys.sort(key=_make_cmp_key, reverse=True) # reverse sort on length and by keyword # if in double quotes, just use % codes for (code, keyword) in d_keys: exp, keyword, ikeyword = d[code] if not isinstance(keyword, UNITYPE): keyword = conv_to_unicode(keyword, "utf-8") format_str = format_str.replace(keyword, "%"+ code) format_str = format_str.replace(keyword.title(), "%"+ code) format_str = format_str.replace(keyword.upper(), "%"+ code.upper()) # Get lower and upper versions of codes: codes = list(d.keys()) + [c.upper() for c in d] # Next, list out the matching patterns: # If it starts with "!" however, treat the punctuation verbatim: if len(format_str) > 0 and format_str[0] == "!": patterns = ["%(" + ("|".join(codes)) + ")", # %s ] format_str = format_str[1:] else: patterns = [ ",\W*\"%(" + ("|".join(codes)) + ")\"", # ,\W*"%s" ",\W*\(%(" + ("|".join(codes)) + ")\)", # ,\W*(%s) ",\W*%(" + ("|".join(codes)) + ")", # ,\W*%s "\"%(" + ("|".join(codes)) + ")\"", # "%s" "_%(" + ("|".join(codes)) + ")_", # _%s_ "\(%(" + ("|".join(codes)) + ")\)", # (%s) "%(" + ("|".join(codes)) + ")", # %s ] new_fmt = format_str # replace the specific format string flags with a # flag that works in standard python format strings. new_fmt = re.sub("|".join(patterns), "%s", new_fmt) # replace special meaning codes we need to have verbatim in output if (len(new_fmt) > 2 and new_fmt[0] == new_fmt[-1] == '"'): new_fmt = new_fmt.replace('\\', r'\\') new_fmt = new_fmt[1:-1].replace('"', r'\"') else: new_fmt = new_fmt.replace('\\', r'\\') new_fmt = new_fmt.replace('"', '\\\"') # find each format flag in the original format string # for each one we find the variable name that is needed to # replace it and add this to a list. This list will be used to # generate the replacement tuple. # This compiled pattern should match all of the format codes. pat = re.compile("|".join(patterns)) param = () mat = pat.search(format_str) while mat: match_pattern = mat.group(0) # the matching pattern # prefix, code, suffix: p, code, s = re.split("%(.)", match_pattern) if code in '0123456789': code = code + s[0] s = s[1:] field = d[code.lower()][0] if code.isupper(): field += ".upper()" if p == '' and s == '': param = param + (field,) else: param = param + ("ifNotEmpty(%s,'%s','%s')" % (field, p, s), ) mat = pat.search(format_str, mat.end()) s = """ def fn(%s): def ifNotEmpty(str,p,s): if str == '': return '' else: return p + str + s return cleanup_name("%s" %% (%s))""" % (args, new_fmt, ",".join(param)) try: exec(s) in globals(), locals() return locals()['fn'] except: LOG.error("\n" + 'Wrong name format string %s' % new_fmt +"\n" + ("ERROR, Edit Name format in Preferences->Display to correct") +"\n" + _('Wrong name format string %s') % new_fmt +"\n" + ("ERROR, Edit Name format in Preferences->Display to correct") ) def errfn(*arg): return _("ERROR, Edit Name format in Preferences") return errfn
displayer = NameDisplay()