gramps/src/BasicUtils/_NameDisplay.py
Doug Blank addfde0e5f 2007-10-18 Douglas S. Blank <dblank@cs.brynmawr.edu>
* src/BasicUtils/_NameDisplay.py: bug in making patterns with uppercase



svn: r9210
2007-10-19 00:14:13 +00:00

591 lines
21 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2004-2007 Donald N. Allingham
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"""
Class handling language-specific displaying of names.
"""
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
from gettext import gettext as _
import re
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from gen.lib import Name
from Errors import NameDisplayError
try:
import Config
WITH_GRAMPS_CONFIG=True
except ImportError:
WITH_GRAMPS_CONFIG=False
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
_FIRSTNAME = 4
_SURNAME = 5
_SUFFIX = 6
_TITLE = 7
_TYPE = 8
_PREFIX = 9
_PATRONYM = 10
_GROUP = 11
_SORT = 12
_DISPLAY = 13
_CALL = 14
_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
#-------------------------------------------------------------------------
#
# Local functions
#
#-------------------------------------------------------------------------
# Because of occurring in an exec(), this couldn't be in a lambda:
def _make_cmp(a,b): return -cmp(a[1], b[1])
#-------------------------------------------------------------------------
#
# NameDisplay class
#
#-------------------------------------------------------------------------
class NameDisplay:
"""
Base class for displaying of Name instances.
"""
format_funcs = {}
raw_format_funcs = {}
# FIXME: Is this used anywhere? I cannot see that it is.
sort_field = (Name.get_surname, Name.get_surname,
Name.get_first_name, Name.get_patronymic,
Name.get_first_name)
STANDARD_FORMATS = [
(Name.DEF,_("Default format (defined by GRAMPS preferences)"),'',_ACT),
(Name.LNFN,_("Family name, Given name Patronymic"),'%p %l, %f %y %s',_ACT),
(Name.FNLN,_("Given name Family name"),'%f %y %p %l %s',_ACT),
(Name.PTFN,_("Patronymic, Given name"),'%p %y, %s %f',_ACT),
(Name.FN,_("Given name"),'%f',_ACT)
]
def __init__(self):
global WITH_GRAMP_CONFIG
self.name_formats = {}
self.set_name_format(self.STANDARD_FORMATS)
if WITH_GRAMPS_CONFIG:
self.default_format = Config.get(Config.NAME_FORMAT)
if self.default_format == 0:
self.default_format = Name.LNFN
Config.set(Config.NAME_FORMAT,self.default_format)
else:
self.default_format = 1
self.set_default_format(self.default_format)
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 = "%s %s, %s %s %s" % (raw_data[_PREFIX],
raw_data[_SURNAME],
raw_data[_FIRSTNAME],
raw_data[_PATRONYM],
raw_data[_SUFFIX])
return ' '.join(result.split())
def _raw_fnln(self,raw_data):
result = "%s %s %s %s %s" % (raw_data[_FIRSTNAME],
raw_data[_PATRONYM],
raw_data[_PREFIX],
raw_data[_SURNAME],
raw_data[_SUFFIX])
return ' '.join(result.split())
def _raw_ptfn(self,raw_data):
result = "%s %s, %s %s" % (raw_data[_PREFIX],
raw_data[_PATRONYM],
raw_data[_SUFFIX],
raw_data[_FIRSTNAME])
return ' '.join(result.split())
def _raw_fn(self,raw_data):
result = raw_data[_FIRSTNAME]
return ' '.join(result.split())
def set_name_format(self,formats):
raw_func_dict = {
Name.LNFN : self._raw_lnfn,
Name.FNLN : self._raw_fnln,
Name.PTFN : self._raw_ptfn,
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)
if func_raw == None:
func_raw = self._format_raw_fn(fmt_str)
self.name_formats[num] = (name,fmt_str,act,func,func_raw)
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
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)
def del_name_format(self,num):
try:
del self.name_formats[num]
except:
pass
def set_default_format(self,num):
if num not in self.name_formats:
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])
def get_default_format(self):
return self.default_format
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
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 = self.name_formats.keys()
keys.sort(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
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 particualar 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 %s %s" % (raw_data[_TITLE],
raw_data[_FIRSTNAME],
raw_data[_PREFIX],
raw_data[_SURNAME],
raw_data[_SUFFIX])
"""
# 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", _("title")),
"f": ("raw_data[_FIRSTNAME]", "given", _("given")),
"p": ("raw_data[_PREFIX]", "prefix", _("prefix")),
"l": ("raw_data[_SURNAME]", "surname", _("surname")),
"s": ("raw_data[_SUFFIX]", "suffix", _("suffix")),
"y": ("raw_data[_PATRONYM]", "patronymic", _("patronymic")),
"c": ("raw_data[_CALL]", "call", _("call")),
"x": ("(raw_data[_CALL] or raw_data[_FIRSTNAME].split(' ')[0])",
"common",
_("common")),
"i": ("''.join([word[0] +'.' for word in ('. ' +" +
" raw_data[_FIRSTNAME]).split()][1:])",
"initials",
_("initials"))
}
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 particualar 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,surname,prefix,suffix,patronymic,title,call,):
return "%s %s %s %s %s" % (first,surname,prefix,suffix,patronymic)
"""
# 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", _("title")),
"f": ("first", "given", _("given")),
"p": ("prefix", "prefix", _("prefix")),
"l": ("surname", "surname", _("surname")),
"s": ("suffix", "suffix", _("suffix")),
"y": ("patronymic", "patronymic", _("patronymic")),
"c": ("call", "call", _("call")),
"x": ("(call or first.split(' ')[0])", "common", _("common")),
"i": ("''.join([word[0] +'.' for word in ('. ' + first).split()][1:])",
"initials", _("initials"))
}
args = "first,surname,prefix,suffix,patronymic,title,call"
return self._make_fn(format_str, d, args)
def _make_fn(self, format_str, d, args):
"""
Creates the name display function and handles dependent
punctuation.
"""
# First, go through and do internationalization-based
# key-word replacement. Just replace ikeywords with
# %codes (ie, replace "irstnamefay" with "%f", and
# "IRSTNAMEFAY" for %F)
d_keys = [(code, d[code][2]) for code in d.keys()]
d_keys.sort(_make_cmp) # reverse sort by ikeyword
for (code, ikeyword) in d_keys:
exp, keyword, ikeyword = d[code]
format_str = format_str.replace(ikeyword,"%"+ 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)
d_keys = [(code, d[code][1]) for code in d.keys()]
d_keys.sort(_make_cmp) # reverse sort by keyword
for (code, keyword) in d_keys:
exp, keyword, ikeyword = d[code]
format_str = format_str.replace(keyword,"%"+ code)
format_str = format_str.replace(keyword.upper(),"%"+ code.upper())
# Get lower and upper versions of codes:
codes = d.keys() + [c.upper() for c in d.keys()]
# Next, list out the matching patterns:
# If it starts with "!" however, treat the punctuation verbatim:
if len(format_str) > 0 and format_str[0] == "!":
format_str = format_str[1:]
patterns = ["%(" + ("|".join(codes)) + ")", # %s
]
else:
patterns = [",\W*\(%(" + ("|".join(codes)) + ")\)", # ,\W*(%s)
",\W*%(" + ("|".join(codes)) + ")", # ,\W*%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)
# 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
# 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)
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 "%s" %% (%s)""" % (args, new_fmt, ",".join(param))
exec(s)
return fn
def format_str(self,name,format_str):
return self._format_str_base(name.first_name,name.surname,name.prefix,
name.suffix,name.patronymic,name.title,
name.call,format_str)
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 _gen_raw_func to generate a new method for each new format_string.
Is does not call _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 == None:
func = self._gen_raw_func(format_str)
self.__class__.raw_format_funcs[format_str] = func
s = func(raw_data)
return ' '.join(s.split())
def _format_str_base(self,first,surname,prefix,suffix,patronymic,
title,call,format_str):
"""
Generates name from a format string.
The following substitutions are made:
%t -> title
%f -> given name (first name)
%p -> prefix
%s -> suffix
%l -> family name (last name, surname)
%y -> patronymic
%c -> call 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 == None:
func = self._gen_cooked_func(format_str)
self.__class__.format_funcs[format_str] = func
try:
s = func(first,surname,prefix,suffix,patronymic,title,call)
except (ValueError,TypeError,):
raise NameDisplayError, "Incomplete format string"
return ' '.join(s.split())
#-------------------------------------------------------------------------
def sort_string(self,name):
return u"%-25s%-30s%s" % (name.surname,name.first_name,name.suffix)
def sorted(self,person):
"""
Returns a text string representing the L{gen.lib.Person} instance's
L{Name} in a manner that should be used for displaying a sorted
name.
@param person: L{gen.lib.Person} instance that contains the
L{Name} that is to be displayed. The primary name is used for
the display.
@type person: L{gen.lib.Person}
@returns: Returns the L{gen.lib.Person} instance's name
@rtype: str
"""
name = person.get_primary_name()
return self.sorted_name(name)
def sorted_name(self,name):
"""
Returns a text string representing the L{Name} instance
in a manner that should be used for displaying a sorted
name.
@param name: L{Name} instance that is to be displayed.
@type name: L{Name}
@returns: Returns the L{Name} string representation
@rtype: str
"""
num = self._is_format_valid(name.sort_as)
return self.name_formats[num][_F_FN](name)
def raw_sorted_name(self,raw_data):
"""
Returns a text string representing the L{Name} instance
in a manner that should be used for displaying a sorted
name.
@param name: L{Name} instance that is to be displayed.
@type name: L{Name}
@returns: Returns the L{Name} string representation
@rtype: str
"""
num = self._is_format_valid(raw_data[_SORT])
return self.name_formats[num][_F_RAWFN](raw_data)
def display(self,person):
"""
Returns a text string representing the L{gen.lib.Person} instance's
L{Name} in a manner that should be used for normal displaying.
@param person: L{gen.lib.Person} instance that contains the
L{Name} that is to be displayed. The primary name is used for
the display.
@type person: L{gen.lib.Person}
@returns: Returns the L{gen.lib.Person} instance's name
@rtype: str
"""
name = person.get_primary_name()
return self.display_name(name)
def display_formal(self,person):
"""
Returns a text string representing the L{gen.lib.Person} instance's
L{Name} in a manner that should be used for normal displaying.
@param person: L{gen.lib.Person} instance that contains the
L{Name} that is to be displayed. The primary name is used for
the display.
@type person: L{gen.lib.Person}
@returns: Returns the L{gen.lib.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)
def display_name(self,name):
"""
Returns a text string representing the L{Name} instance
in a manner that should be used for normal displaying.
@param name: L{Name} instance that is to be displayed.
@type name: L{Name}
@returns: Returns the L{Name} string representation
@rtype: str
"""
if name == None:
return ""
num = self._is_format_valid(name.display_as)
return self.name_formats[num][_F_FN](name)
def display_given(self,person):
name = person.get_primary_name()
return self.format_str(person.get_primary_name(),'%f %y')
def name_grouping(self,db,person):
return self.name_grouping_name(db,person.primary_name)
def name_grouping_name(self,db,pn):
if pn.group_as:
return pn.group_as
sv = pn.sort_as
if sv == Name.LNFN or sv == Name.DEF:
return db.get_name_group_mapping(pn.surname)
elif sv == Name.PTFN:
return db.get_name_group_mapping(pn.patronymic)
else:
return db.get_name_group_mapping(pn.first_name)
def name_grouping_data(self, db, pn):
if pn[_GROUP]:
return pn[_GROUP]
sv = pn[_SORT]
if sv == Name.LNFN or sv == Name.DEF:
return db.get_name_group_mapping(pn[_SURNAME])
elif sv == Name.PTFN:
return db.get_name_group_mapping(pn[_PATRONYM])
else:
return db.get_name_group_mapping(pn[_FIRSTNAME])
displayer = NameDisplay()