1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2004-2007 Donald N. Allingham 5# Copyright (C) 2010 Brian G. Matherly 6# Copyright (C) 2014 Paul Franklin 7# 8# This program is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program; if not, write to the Free Software 20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21# 22 23""" 24Class handling language-specific displaying of names. 25 26Specific symbols for parts of a name are defined: 27 28 ====== =============================================================== 29 Symbol Description 30 ====== =============================================================== 31 't' title 32 'f' given (first names) 33 'l' full surname (lastname) 34 'c' callname 35 'x' nick name, call, or otherwise first first name (common name) 36 'i' initials of the first names 37 'm' primary surname (main) 38 '0m' primary surname prefix 39 '1m' primary surname surname 40 '2m' primary surname connector 41 'y' pa/matronymic surname (father/mother) - assumed unique 42 '0y' pa/matronymic prefix 43 '1y' pa/matronymic surname 44 '2y' pa/matronymic connector 45 'o' surnames without pa/matronymic and primary 46 'r' non primary surnames (rest) 47 'p' list of all prefixes 48 'q' surnames without prefixes and connectors 49 's' suffix 50 'n' nick name 51 'g' family nick name 52 ====== =============================================================== 53""" 54 55#------------------------------------------------------------------------- 56# 57# Python modules 58# 59#------------------------------------------------------------------------- 60import re 61import logging 62LOG = logging.getLogger(".gramps.gen") 63 64#------------------------------------------------------------------------- 65# 66# Gramps modules 67# 68#------------------------------------------------------------------------- 69from ..const import ARABIC_COMMA, ARABIC_SEMICOLON, GRAMPS_LOCALE as glocale 70_ = glocale.translation.sgettext 71from ..lib.name import Name 72from ..lib.nameorigintype import NameOriginType 73 74try: 75 from ..config import config 76 WITH_GRAMPS_CONFIG=True 77except ImportError: 78 WITH_GRAMPS_CONFIG=False 79 80 81#------------------------------------------------------------------------- 82# 83# Constants 84# 85#------------------------------------------------------------------------- 86_FIRSTNAME = 4 87_SURNAME_LIST = 5 88_SUFFIX = 6 89_TITLE = 7 90_TYPE = 8 91_GROUP = 9 92_SORT = 10 93_DISPLAY = 11 94_CALL = 12 95_NICK = 13 96_FAMNICK = 14 97_SURNAME_IN_LIST = 0 98_PREFIX_IN_LIST = 1 99_PRIMARY_IN_LIST = 2 100_TYPE_IN_LIST = 3 101_CONNECTOR_IN_LIST = 4 102_ORIGINPATRO = NameOriginType.PATRONYMIC 103_ORIGINMATRO = NameOriginType.MATRONYMIC 104 105_ACT = True 106_INA = False 107 108_F_NAME = 0 # name of the format 109_F_FMT = 1 # the format string 110_F_ACT = 2 # if the format is active 111_F_FN = 3 # name format function 112_F_RAWFN = 4 # name format raw function 113 114PAT_AS_SURN = False 115 116#------------------------------------------------------------------------- 117# 118# Local functions 119# 120#------------------------------------------------------------------------- 121# Because of occurring in an exec(), this couldn't be in a lambda: 122# we sort names first on longest first, then last letter first, this to 123# avoid translations of shorter terms which appear in longer ones, eg 124# namelast may not be mistaken with name, so namelast must first be 125# converted to %k before name is converted. 126##def _make_cmp(a, b): return -cmp((len(a[1]),a[1]), (len(b[1]), b[1])) 127def _make_cmp_key(a): return (len(a[1]),a[1]) # set reverse to True!! 128 129#------------------------------------------------------------------------- 130# 131# NameDisplayError class 132# 133#------------------------------------------------------------------------- 134class NameDisplayError(Exception): 135 """ 136 Error used to report that the name display format string is invalid. 137 """ 138 def __init__(self, value): 139 Exception.__init__(self) 140 self.value = value 141 142 def __str__(self): 143 return self.value 144 145#------------------------------------------------------------------------- 146# 147# Functions to extract data from raw lists (unserialized objects) 148# 149#------------------------------------------------------------------------- 150 151def _raw_full_surname(raw_surn_data_list): 152 """method for the 'l' symbol: full surnames""" 153 result = "" 154 for raw_surn_data in raw_surn_data_list: 155 result += "%s %s %s " % (raw_surn_data[_PREFIX_IN_LIST], 156 raw_surn_data[_SURNAME_IN_LIST], 157 raw_surn_data[_CONNECTOR_IN_LIST]) 158 return ' '.join(result.split()).strip() 159 160def _raw_primary_surname(raw_surn_data_list): 161 """method for the 'm' symbol: primary surname""" 162 global PAT_AS_SURN 163 nrsur = len(raw_surn_data_list) 164 for raw_surn_data in raw_surn_data_list: 165 if raw_surn_data[_PRIMARY_IN_LIST]: 166 #if there are multiple surnames, return the primary. If there 167 #is only one surname, then primary has little meaning, and we 168 #assume a pa/matronymic should not be given as primary as it 169 #normally is defined independently 170 if not PAT_AS_SURN and nrsur == 1 and \ 171 (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO 172 or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): 173 return '' 174 else: 175 result = "%s %s %s" % (raw_surn_data[_PREFIX_IN_LIST], 176 raw_surn_data[_SURNAME_IN_LIST], 177 raw_surn_data[_CONNECTOR_IN_LIST]) 178 return ' '.join(result.split()) 179 return '' 180 181def _raw_primary_surname_only(raw_surn_data_list): 182 """method to obtain the raw primary surname data, so this returns a string 183 """ 184 global PAT_AS_SURN 185 nrsur = len(raw_surn_data_list) 186 for raw_surn_data in raw_surn_data_list: 187 if raw_surn_data[_PRIMARY_IN_LIST]: 188 if not PAT_AS_SURN and nrsur == 1 and \ 189 (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO 190 or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): 191 return '' 192 else: 193 return raw_surn_data[_SURNAME_IN_LIST] 194 return '' 195 196def _raw_primary_prefix_only(raw_surn_data_list): 197 """method to obtain the raw primary surname data""" 198 global PAT_AS_SURN 199 nrsur = len(raw_surn_data_list) 200 for raw_surn_data in raw_surn_data_list: 201 if raw_surn_data[_PRIMARY_IN_LIST]: 202 if not PAT_AS_SURN and nrsur == 1 and \ 203 (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO 204 or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): 205 return '' 206 else: 207 return raw_surn_data[_PREFIX_IN_LIST] 208 return '' 209 210def _raw_primary_conn_only(raw_surn_data_list): 211 """method to obtain the raw primary surname data""" 212 global PAT_AS_SURN 213 nrsur = len(raw_surn_data_list) 214 for raw_surn_data in raw_surn_data_list: 215 if raw_surn_data[_PRIMARY_IN_LIST]: 216 if not PAT_AS_SURN and nrsur == 1 and \ 217 (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO 218 or raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): 219 return '' 220 else: 221 return raw_surn_data[_CONNECTOR_IN_LIST] 222 return '' 223 224def _raw_patro_surname(raw_surn_data_list): 225 """method for the 'y' symbol: patronymic surname""" 226 for raw_surn_data in raw_surn_data_list: 227 if (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or 228 raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): 229 result = "%s %s %s" % (raw_surn_data[_PREFIX_IN_LIST], 230 raw_surn_data[_SURNAME_IN_LIST], 231 raw_surn_data[_CONNECTOR_IN_LIST]) 232 return ' '.join(result.split()) 233 return '' 234 235def _raw_patro_surname_only(raw_surn_data_list): 236 """method for the '1y' symbol: patronymic surname only""" 237 for raw_surn_data in raw_surn_data_list: 238 if (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or 239 raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): 240 result = "%s" % (raw_surn_data[_SURNAME_IN_LIST]) 241 return ' '.join(result.split()) 242 return '' 243 244def _raw_patro_prefix_only(raw_surn_data_list): 245 """method for the '0y' symbol: patronymic prefix only""" 246 for raw_surn_data in raw_surn_data_list: 247 if (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or 248 raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): 249 result = "%s" % (raw_surn_data[_PREFIX_IN_LIST]) 250 return ' '.join(result.split()) 251 return '' 252 253def _raw_patro_conn_only(raw_surn_data_list): 254 """method for the '2y' symbol: patronymic conn only""" 255 for raw_surn_data in raw_surn_data_list: 256 if (raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINPATRO or 257 raw_surn_data[_TYPE_IN_LIST][0] == _ORIGINMATRO): 258 result = "%s" % (raw_surn_data[_CONNECTOR_IN_LIST]) 259 return ' '.join(result.split()) 260 return '' 261 262def _raw_nonpatro_surname(raw_surn_data_list): 263 """method for the 'o' symbol: full surnames without pa/matronymic or 264 primary 265 """ 266 result = "" 267 for raw_surn_data in raw_surn_data_list: 268 if ((not raw_surn_data[_PRIMARY_IN_LIST]) and 269 raw_surn_data[_TYPE_IN_LIST][0] != _ORIGINPATRO and 270 raw_surn_data[_TYPE_IN_LIST][0] != _ORIGINMATRO): 271 result += "%s %s %s " % (raw_surn_data[_PREFIX_IN_LIST], 272 raw_surn_data[_SURNAME_IN_LIST], 273 raw_surn_data[_CONNECTOR_IN_LIST]) 274 return ' '.join(result.split()).strip() 275 276def _raw_nonprimary_surname(raw_surn_data_list): 277 """method for the 'r' symbol: nonprimary surnames""" 278 result = '' 279 for raw_surn_data in raw_surn_data_list: 280 if not raw_surn_data[_PRIMARY_IN_LIST]: 281 result = "%s %s %s %s" % (result, raw_surn_data[_PREFIX_IN_LIST], 282 raw_surn_data[_SURNAME_IN_LIST], 283 raw_surn_data[_CONNECTOR_IN_LIST]) 284 return ' '.join(result.split()) 285 286def _raw_prefix_surname(raw_surn_data_list): 287 """method for the 'p' symbol: all prefixes""" 288 result = "" 289 for raw_surn_data in raw_surn_data_list: 290 result += "%s " % (raw_surn_data[_PREFIX_IN_LIST]) 291 return ' '.join(result.split()).strip() 292 293def _raw_single_surname(raw_surn_data_list): 294 """method for the 'q' symbol: surnames without prefix and connectors""" 295 result = "" 296 for raw_surn_data in raw_surn_data_list: 297 result += "%s " % (raw_surn_data[_SURNAME_IN_LIST]) 298 return ' '.join(result.split()).strip() 299 300def cleanup_name(namestring): 301 """Remove too long white space due to missing name parts, 302 so "a b" becomes "a b" and "a , b" becomes "a, b" 303 """ 304 parts = namestring.split() 305 if not parts: 306 return "" 307 result = parts[0] 308 for val in parts[1:]: 309 if len(val) == 1 and val in [',', ';', ':', 310 ARABIC_COMMA, ARABIC_SEMICOLON]: 311 result += val 312 else: 313 result += ' ' + val 314 return result 315 316#------------------------------------------------------------------------- 317# 318# NameDisplay class 319# 320#------------------------------------------------------------------------- 321class NameDisplay: 322 """ 323 Base class for displaying of Name instances. 324 325 Property: 326 *default_format* 327 the default name format to use 328 *pas_as_surn* 329 if only one surname, see if pa/ma should be considered as 'the' surname. 330 """ 331 332 format_funcs = {} 333 raw_format_funcs = {} 334 335 def __init__(self, xlocale=glocale): 336 """ 337 Initialize the NameDisplay class. 338 339 If xlocale is passed in (a GrampsLocale), then 340 the translated script will be returned instead. 341 342 :param xlocale: allow selection of the displayer script 343 :type xlocale: a GrampsLocale instance 344 """ 345 global WITH_GRAMPS_CONFIG 346 global PAT_AS_SURN 347 348 # translators: needed for Arabic, ignore otherwise 349 COMMAGLYPH = xlocale.translation.gettext(',') 350 351 self.STANDARD_FORMATS = [ 352 (Name.DEF, _("Default format (defined by Gramps preferences)"), 353 '', _ACT), 354 (Name.LNFN, _("Surname, Given Suffix"), 355 '%l' + COMMAGLYPH + ' %f %s', _ACT), 356 (Name.FN, _("Given"), 357 '%f', _ACT), 358 (Name.FNLN, _("Given Surname Suffix"), 359 '%f %l %s', _ACT), 360 # primary name primconnector other, given pa/matronynic suffix, primprefix 361 # translators: long string, have a look at Preferences dialog 362 (Name.LNFNP, _("Main Surnames, Given Patronymic Suffix Prefix"), 363 '%1m %2m %o' + COMMAGLYPH + ' %f %1y %s %0m', _ACT), 364 # DEPRECATED FORMATS 365 (Name.PTFN, _("Patronymic, Given"), 366 '%y' + COMMAGLYPH + ' %s %f', _INA), 367 ] 368 369 self.LNFN_STR = "%s" + COMMAGLYPH + " %s %s" 370 371 self.name_formats = {} 372 373 if WITH_GRAMPS_CONFIG: 374 self.default_format = config.get('preferences.name-format') 375 if self.default_format == 0: 376 self.default_format = Name.LNFN 377 config.set('preferences.name-format', self.default_format) 378 #if only one surname, see if pa/ma should be considered as 379 # 'the' surname. 380 PAT_AS_SURN = config.get('preferences.patronimic-surname') 381 config.connect('preferences.patronimic-surname', self.change_pa_sur) 382 else: 383 self.default_format = Name.LNFN 384 PAT_AS_SURN = False 385 386 #preinit the name formats, this should be updated with the data 387 #in the database once a database is loaded 388 self.set_name_format(self.STANDARD_FORMATS) 389 390 def change_pa_sur(self, *args): 391 """ How to handle single patronymic as surname is changed""" 392 global PAT_AS_SURN 393 PAT_AS_SURN = config.get('preferences.patronimic-surname') 394 395 def get_pat_as_surn(self): 396 global PAT_AS_SURN 397 return PAT_AS_SURN 398 399 def _format_fn(self, fmt_str): 400 return lambda x: self.format_str(x, fmt_str) 401 402 def _format_raw_fn(self, fmt_str): 403 return lambda x: self.format_str_raw(x, fmt_str) 404 405 def _raw_lnfn(self, raw_data): 406 result = self.LNFN_STR % (_raw_full_surname(raw_data[_SURNAME_LIST]), 407 raw_data[_FIRSTNAME], 408 raw_data[_SUFFIX]) 409 return ' '.join(result.split()) 410 411 def _raw_fnln(self, raw_data): 412 result = "%s %s %s" % (raw_data[_FIRSTNAME], 413 _raw_full_surname(raw_data[_SURNAME_LIST]), 414 raw_data[_SUFFIX]) 415 return ' '.join(result.split()) 416 417 def _raw_fn(self, raw_data): 418 result = raw_data[_FIRSTNAME] 419 return ' '.join(result.split()) 420 421 def clear_custom_formats(self): 422 self.name_formats = {num: value 423 for num, value in self.name_formats.items() 424 if num >= 0} 425 426 def set_name_format(self, formats): 427 428 raw_func_dict = { 429 Name.LNFN : self._raw_lnfn, 430 Name.FNLN : self._raw_fnln, 431 Name.FN : self._raw_fn, 432 } 433 434 for (num, name, fmt_str, act) in formats: 435 func = self._format_fn(fmt_str) 436 func_raw = raw_func_dict.get(num, self._format_raw_fn(fmt_str)) 437 self.name_formats[num] = (name, fmt_str, act, func, func_raw) 438 self.set_default_format(self.get_default_format()) 439 440 def add_name_format(self, name, fmt_str): 441 for num in self.name_formats: 442 if fmt_str in self.name_formats.get(num): 443 return num 444 num = -1 445 while num in self.name_formats: 446 num -= 1 447 self.set_name_format([(num, name, fmt_str,_ACT)]) 448 return num 449 450 def edit_name_format(self, num, name, fmt_str): 451 self.set_name_format([(num, name, fmt_str,_ACT)]) 452 if self.default_format == num: 453 self.set_default_format(num) 454 455 def del_name_format(self, num): 456 try: 457 del self.name_formats[num] 458 except: 459 pass 460 461 def set_default_format(self, num): 462 if num not in self.name_formats: 463 num = Name.LNFN 464 # if user sets default format to the Gramps default format, 465 # then we select LNFN as format. 466 if num == Name.DEF: 467 num = Name.LNFN 468 469 self.default_format = num 470 471 self.name_formats[Name.DEF] = (self.name_formats[Name.DEF][_F_NAME], 472 self.name_formats[Name.DEF][_F_FMT], 473 self.name_formats[Name.DEF][_F_ACT], 474 self.name_formats[num][_F_FN], 475 self.name_formats[num][_F_RAWFN]) 476 477 def get_default_format(self): 478 return self.default_format 479 480 def set_format_inactive(self, num): 481 try: 482 self.name_formats[num] = (self.name_formats[num][_F_NAME], 483 self.name_formats[num][_F_FMT], 484 _INA, 485 self.name_formats[num][_F_FN], 486 self.name_formats[num][_F_RAWFN]) 487 except: 488 pass 489 490 def get_name_format(self, also_default=False, 491 only_custom=False, 492 only_active=True): 493 """ 494 Get a list of tuples (num, name,fmt_str,act) 495 """ 496 the_list = [] 497 498 keys = sorted(self.name_formats, key=self.cmp_to_key(self._sort_name_format)) 499 500 for num in keys: 501 if ((also_default or num) and 502 (not only_custom or (num < 0)) and 503 (not only_active or self.name_formats[num][_F_ACT])): 504 the_list.append((num,) + self.name_formats[num][_F_NAME:_F_FN]) 505 506 return the_list 507 508 def cmp_to_key(self, mycmp): 509 """ 510 python 2 to 3 conversion, python recipe http://code.activestate.com/recipes/576653/ 511 Convert a :func:`cmp` function into a :func:`key` function 512 We use this in Gramps as understanding the old compare function is 513 not trivial. This should be replaced by a proper key function 514 """ 515 class K: 516 def __init__(self, obj, *args): 517 self.obj = obj 518 def __lt__(self, other): 519 return mycmp(self.obj, other.obj) < 0 520 def __gt__(self, other): 521 return mycmp(self.obj, other.obj) > 0 522 def __eq__(self, other): 523 return mycmp(self.obj, other.obj) == 0 524 def __le__(self, other): 525 return mycmp(self.obj, other.obj) <= 0 526 def __ge__(self, other): 527 return mycmp(self.obj, other.obj) >= 0 528 def __ne__(self, other): 529 return mycmp(self.obj, other.obj) != 0 530 return K 531 def _sort_name_format(self, x, y): 532 if x < 0: 533 if y < 0: 534 return x+y 535 else: 536 return -x+y 537 else: 538 if y < 0: 539 return -x+y 540 else: 541 return x-y 542 543 def _is_format_valid(self, num): 544 try: 545 if not self.name_formats[num][_F_ACT]: 546 num = 0 547 except: 548 num = 0 549 return num 550 551 #------------------------------------------------------------------------- 552 553 554 def _gen_raw_func(self, format_str): 555 """The job of building the name from a format string is rather 556 expensive and it is called lots and lots of times. So it is worth 557 going to some length to optimise it as much as possible. 558 559 This method constructs a new function that is specifically written 560 to format a name given a particular format string. This is worthwhile 561 because the format string itself rarely changes, so by caching the new 562 function and calling it directly when asked to format a name to the 563 same format string again we can be as quick as possible. 564 565 The new function is of the form:: 566 567 def fn(raw_data): 568 return "%s %s %s" % (raw_data[_TITLE], 569 raw_data[_FIRSTNAME], 570 raw_data[_SUFFIX]) 571 572 Specific symbols for parts of a name are defined (keywords given): 573 't' : title = title 574 'f' : given = given (first names) 575 'l' : surname = full surname (lastname) 576 'c' : call = callname 577 'x' : common = nick name, call, otherwise first first name (common name) 578 'i' : initials = initials of the first names 579 'm' : primary = primary surname (main) 580 '0m': primary[pre]= prefix primary surname (main) 581 '1m': primary[sur]= surname primary surname (main) 582 '2m': primary[con]= connector primary surname (main) 583 'y' : patronymic = pa/matronymic surname (father/mother) - assumed unique 584 '0y': patronymic[pre] = prefix " 585 '1y': patronymic[sur] = surname " 586 '2y': patronymic[con] = connector " 587 'o' : notpatronymic = surnames without pa/matronymic and primary 588 'r' : rest = non primary surnames 589 'p' : prefix = list of all prefixes 590 'q' : rawsurnames = surnames without prefixes and connectors 591 's' : suffix = suffix 592 'n' : nickname = nick name 593 'g' : familynick = family nick name 594 595 596 """ 597 598 # we need the names of each of the variables or methods that are 599 # called to fill in each format flag. 600 # Dictionary is "code": ("expression", "keyword", "i18n-keyword") 601 d = {"t": ("raw_data[_TITLE]", "title", 602 _("Person|title")), 603 "f": ("raw_data[_FIRSTNAME]", "given", 604 _("given")), 605 "l": ("_raw_full_surname(raw_data[_SURNAME_LIST])", "surname", 606 _("surname")), 607 "s": ("raw_data[_SUFFIX]", "suffix", 608 _("suffix")), 609 "c": ("raw_data[_CALL]", "call", 610 _("Name|call")), 611 "x": ("(raw_data[_NICK] or raw_data[_CALL] or raw_data[_FIRSTNAME].split(' ')[0])", 612 "common", 613 _("Name|common")), 614 "i": ("''.join([word[0] +'.' for word in ('. ' +" + 615 " raw_data[_FIRSTNAME]).split()][1:])", 616 "initials", 617 _("initials")), 618 "m": ("_raw_primary_surname(raw_data[_SURNAME_LIST])", 619 "primary", 620 _("Name|primary")), 621 "0m": ("_raw_primary_prefix_only(raw_data[_SURNAME_LIST])", 622 "primary[pre]", 623 _("primary[pre]")), 624 "1m": ("_raw_primary_surname_only(raw_data[_SURNAME_LIST])", 625 "primary[sur]", 626 _("primary[sur]")), 627 "2m": ("_raw_primary_conn_only(raw_data[_SURNAME_LIST])", 628 "primary[con]", 629 _("primary[con]")), 630 "y": ("_raw_patro_surname(raw_data[_SURNAME_LIST])", "patronymic", 631 _("patronymic")), 632 "0y": ("_raw_patro_prefix_only(raw_data[_SURNAME_LIST])", "patronymic[pre]", 633 _("patronymic[pre]")), 634 "1y": ("_raw_patro_surname_only(raw_data[_SURNAME_LIST])", "patronymic[sur]", 635 _("patronymic[sur]")), 636 "2y": ("_raw_patro_conn_only(raw_data[_SURNAME_LIST])", "patronymic[con]", 637 _("patronymic[con]")), 638 "o": ("_raw_nonpatro_surname(raw_data[_SURNAME_LIST])", "notpatronymic", 639 _("notpatronymic")), 640 "r": ("_raw_nonprimary_surname(raw_data[_SURNAME_LIST])", 641 "rest", 642 _("Remaining names|rest")), 643 "p": ("_raw_prefix_surname(raw_data[_SURNAME_LIST])", 644 "prefix", 645 _("prefix")), 646 "q": ("_raw_single_surname(raw_data[_SURNAME_LIST])", 647 "rawsurnames", 648 _("rawsurnames")), 649 "n": ("raw_data[_NICK]", "nickname", 650 _("nickname")), 651 "g": ("raw_data[_FAMNICK]", "familynick", 652 _("familynick")), 653 } 654 args = "raw_data" 655 return self._make_fn(format_str, d, args) 656 657 def _gen_cooked_func(self, format_str): 658 """The job of building the name from a format string is rather 659 expensive and it is called lots and lots of times. So it is worth 660 going to some length to optimise it as much as possible. 661 662 This method constructs a new function that is specifically written 663 to format a name given a particular format string. This is worthwhile 664 because the format string itself rarely changes, so by caching the new 665 function and calling it directly when asked to format a name to the 666 same format string again we can be as quick as possible. 667 668 The new function is of the form:: 669 670 def fn(first, raw_surname_list, suffix, title, call,): 671 return "%s %s" % (first,suffix) 672 673 Specific symbols for parts of a name are defined (keywords given): 674 't' : title = title 675 'f' : given = given (first names) 676 'l' : surname = full surname (lastname) 677 'c' : call = callname 678 'x' : common = nick name, call, or otherwise first first name (common name) 679 'i' : initials = initials of the first names 680 'm' : primary = primary surname (main) 681 '0m': primary[pre]= prefix primary surname (main) 682 '1m': primary[sur]= surname primary surname (main) 683 '2m': primary[con]= connector primary surname (main) 684 'y' : patronymic = pa/matronymic surname (father/mother) - assumed unique 685 '0y': patronymic[pre] = prefix " 686 '1y': patronymic[sur] = surname " 687 '2y': patronymic[con] = connector " 688 'o' : notpatronymic = surnames without pa/matronymic and primary 689 'r' : rest = non primary surnames 690 'p' : prefix = list of all prefixes 691 'q' : rawsurnames = surnames without prefixes and connectors 692 's' : suffix = suffix 693 'n' : nickname = nick name 694 'g' : familynick = family nick name 695 696 """ 697 698 # we need the names of each of the variables or methods that are 699 # called to fill in each format flag. 700 # Dictionary is "code": ("expression", "keyword", "i18n-keyword") 701 d = {"t": ("title", "title", 702 _("Person|title")), 703 "f": ("first", "given", 704 _("given")), 705 "l": ("_raw_full_surname(raw_surname_list)", "surname", 706 _("surname")), 707 "s": ("suffix", "suffix", 708 _("suffix")), 709 "c": ("call", "call", 710 _("Name|call")), 711 "x": ("(nick or call or first.split(' ')[0])", "common", 712 _("Name|common")), 713 "i": ("''.join([word[0] +'.' for word in ('. ' + first).split()][1:])", 714 "initials", 715 _("initials")), 716 "m": ("_raw_primary_surname(raw_surname_list)", "primary", 717 _("Name|primary")), 718 "0m":("_raw_primary_prefix_only(raw_surname_list)", 719 "primary[pre]", _("primary[pre]")), 720 "1m":("_raw_primary_surname_only(raw_surname_list)", 721 "primary[sur]",_("primary[sur]")), 722 "2m":("_raw_primary_conn_only(raw_surname_list)", 723 "primary[con]", _("primary[con]")), 724 "y": ("_raw_patro_surname(raw_surname_list)", "patronymic", 725 _("patronymic")), 726 "0y":("_raw_patro_prefix_only(raw_surname_list)", "patronymic[pre]", 727 _("patronymic[pre]")), 728 "1y":("_raw_patro_surname_only(raw_surname_list)", "patronymic[sur]", 729 _("patronymic[sur]")), 730 "2y":("_raw_patro_conn_only(raw_surname_list)", "patronymic[con]", 731 _("patronymic[con]")), 732 "o": ("_raw_nonpatro_surname(raw_surname_list)", "notpatronymic", 733 _("notpatronymic")), 734 "r": ("_raw_nonprimary_surname(raw_surname_list)", "rest", 735 _("Remaining names|rest")), 736 "p": ("_raw_prefix_surname(raw_surname_list)", "prefix", 737 _("prefix")), 738 "q": ("_raw_single_surname(raw_surname_list)", "rawsurnames", 739 _("rawsurnames")), 740 "n": ("nick", "nickname", 741 _("nickname")), 742 "g": ("famnick", "familynick", 743 _("familynick")), 744 } 745 args = "first,raw_surname_list,suffix,title,call,nick,famnick" 746 return self._make_fn(format_str, d, args) 747 748 def format_str(self, name, format_str): 749 return self._format_str_base(name.first_name, name.surname_list, 750 name.suffix, name.title, 751 name.call, name.nick, name.famnick, 752 format_str) 753 754 def format_str_raw(self, raw_data, format_str): 755 """ 756 Format a name from the raw name list. To make this as fast as possible 757 this uses :func:`_gen_raw_func` to generate a new method for each new 758 format_string. 759 760 Is does not call :meth:`_format_str_base` because it would introduce an 761 extra method call and we need all the speed we can squeeze out of this. 762 """ 763 func = self.__class__.raw_format_funcs.get(format_str) 764 if func is None: 765 func = self._gen_raw_func(format_str) 766 self.__class__.raw_format_funcs[format_str] = func 767 768 return func(raw_data) 769 770 def _format_str_base(self, first, surname_list, suffix, title, call, 771 nick, famnick, format_str): 772 """ 773 Generates name from a format string. 774 775 The following substitutions are made: 776 '%t' : title 777 '%f' : given (first names) 778 '%l' : full surname (lastname) 779 '%c' : callname 780 '%x' : nick name, call, or otherwise first first name (common name) 781 '%i' : initials of the first names 782 '%m' : primary surname (main) 783 '%0m': prefix primary surname (main) 784 '%1m': surname primary surname (main) 785 '%2m': connector primary surname (main) 786 '%y' : pa/matronymic surname (father/mother) - assumed unique 787 '%0y': prefix " 788 '%1y': surname " 789 '%2y': connector " 790 '%o' : surnames without patronymic 791 '%r' : non-primary surnames (rest) 792 '%p' : list of all prefixes 793 '%q' : surnames without prefixes and connectors 794 '%s' : suffix 795 '%n' : nick name 796 '%g' : family nick name 797 The capital letters are substituted for capitalized name components. 798 The %% is substituted with the single % character. 799 All the other characters in the fmt_str are unaffected. 800 """ 801 func = self.__class__.format_funcs.get(format_str) 802 if func is None: 803 func = self._gen_cooked_func(format_str) 804 self.__class__.format_funcs[format_str] = func 805 try: 806 s = func(first, [surn.serialize() for surn in surname_list], 807 suffix, title, call, nick, famnick) 808 except (ValueError, TypeError,): 809 raise NameDisplayError("Incomplete format string") 810 811 return s 812 813 #------------------------------------------------------------------------- 814 815 def primary_surname(self, name): 816 global PAT_AS_SURN 817 nrsur = len(name.surname_list) 818 sur = name.get_primary_surname() 819 if not PAT_AS_SURN and nrsur <= 1 and \ 820 (sur.get_origintype().value == _ORIGINPATRO 821 or sur.get_origintype().value == _ORIGINMATRO): 822 return '' 823 return sur.get_surname() 824 825 def sort_string(self, name): 826 return "%-25s%-30s%s" % (self.primary_surname(name), 827 name.first_name, name.suffix) 828 829 def sorted(self, person): 830 """ 831 Return a text string representing the :class:`~.person.Person` 832 instance's :class:`~.name.Name` in a manner that should be used for 833 displaying a sortedname. 834 835 :param person: :class:`~.person.Person` instance that contains the 836 :class:`~.name.Name` that is to be displayed. The 837 primary name is used for the display. 838 :type person: :class:`~.person.Person` 839 :returns: Returns the :class:`~.person.Person` instance's name 840 :rtype: str 841 """ 842 name = person.get_primary_name() 843 return self.sorted_name(name) 844 845 def sorted_name(self, name): 846 """ 847 Return a text string representing the :class:`~.name.Name` instance 848 in a manner that should be used for sorting the name in a list. 849 850 :param name: :class:`~.name.Name` instance that is to be displayed. 851 :type name: :class:`~.name.Name` 852 :returns: Returns the :class:`~.name.Name` string representation 853 :rtype: str 854 """ 855 num = self._is_format_valid(name.sort_as) 856 return self.name_formats[num][_F_FN](name) 857 858 def truncate(self, full_name, max_length=15, elipsis="..."): 859 name_out = "" 860 if len(full_name) <= max_length: 861 name_out = full_name 862 else: 863 last_space = full_name.rfind(" ", max_length) 864 if (last_space) > -1: 865 name_out = full_name[:last_space] 866 else: 867 name_out = full_name[:max_length] 868 name_out += " " + elipsis 869 return name_out 870 871 def raw_sorted_name(self, raw_data): 872 """ 873 Return a text string representing the :class:`~.name.Name` instance 874 in a manner that should be used for sorting the name in a list. 875 876 :param name: raw unserialized data of name that is to be displayed. 877 :type name: tuple 878 :returns: Returns the :class:`~.name.Name` string representation 879 :rtype: str 880 """ 881 num = self._is_format_valid(raw_data[_SORT]) 882 return self.name_formats[num][_F_RAWFN](raw_data) 883 884 def display(self, person): 885 """ 886 Return a text string representing the :class:`~.person.Person` 887 instance's :class:`~.name.Name` in a manner that should be used for 888 normal displaying. 889 890 :param person: :class:`~.person.Person` instance that contains the 891 :class:`~.name.Name` that is to be displayed. The 892 primary name is used for the display. 893 :type person: :class:`~.person.Person` 894 :returns: Returns the :class:`~.person.Person` instance's name 895 :rtype: str 896 """ 897 name = person.get_primary_name() 898 return self.display_name(name) 899 900 def display_format(self, person, num): 901 """ 902 Return a text string representing the L{gen.lib.Person} instance's 903 L{Name} using num format. 904 905 @param person: L{gen.lib.Person} instance that contains the 906 L{Name} that is to be displayed. The primary name is used for 907 the display. 908 @type person: L{gen.lib.Person} 909 @param num: num of the format to be used, as return by 910 name_displayer.add_name_format('name','format') 911 @type num: int 912 @returns: Returns the L{gen.lib.Person} instance's name 913 @rtype: str 914 """ 915 name = person.get_primary_name() 916 return self.name_formats[num][_F_FN](name) 917 918 def display_formal(self, person): 919 """ 920 Return a text string representing the :class:`~.person.Person` 921 instance's :class:`~.name.Name` in a manner that should be used for 922 formal displaying. 923 924 :param person: :class:`~.person.Person` instance that contains the 925 :class:`~.name.Name` that is to be displayed. The 926 primary name is used for the display. 927 :type person: :class:`~.person.Person` 928 :returns: Returns the :class:`~.person.Person` instance's name 929 :rtype: str 930 """ 931 # FIXME: At this time, this is just duplicating display() method 932 name = person.get_primary_name() 933 return self.display_name(name) 934 935 def display_name(self, name): 936 """ 937 Return a text string representing the :class:`~.name.Name` instance 938 in a manner that should be used for normal displaying. 939 940 :param name: :class:`~.name.Name` instance that is to be displayed. 941 :type name: :class:`~.name.Name` 942 :returns: Returns the :class:`~.name.Name` string representation 943 :rtype: str 944 """ 945 if name is None: 946 return "" 947 948 num = self._is_format_valid(name.display_as) 949 return self.name_formats[num][_F_FN](name) 950 951 def raw_display_name(self, raw_data): 952 """ 953 Return a text string representing the :class:`~.name.Name` instance 954 in a manner that should be used for normal displaying. 955 956 :param name: raw unserialized data of name that is to be displayed. 957 :type name: tuple 958 :returns: Returns the :class:`~.name.Name` string representation 959 :rtype: str 960 """ 961 num = self._is_format_valid(raw_data[_DISPLAY]) 962 return self.name_formats[num][_F_RAWFN](raw_data) 963 964 def display_given(self, person): 965 return self.format_str(person.get_primary_name(),'%f') 966 967 def name_grouping(self, db, person): 968 """ 969 Return the name under which to group this person. This is defined as: 970 971 1. if group name is defined on primary name, use that 972 2. if group name is defined for the primary surname of the primary 973 name, use that 974 3. use primary surname of primary name otherwise 975 """ 976 return self.name_grouping_name(db, person.primary_name) 977 978 def name_grouping_name(self, db, pn): 979 """ 980 Return the name under which to group. This is defined as: 981 982 1. if group name is defined, use that 983 2. if group name is defined for the primary surname, use that 984 3. use primary surname itself otherwise 985 986 :param pn: :class:`~.name.Name` object 987 :type pn: :class:`~.name.Name` instance 988 :returns: Returns the groupname string representation 989 :rtype: str 990 """ 991 if pn.group_as: 992 return pn.group_as 993 return db.get_name_group_mapping(pn.get_primary_surname().get_surname()) 994 995 def name_grouping_data(self, db, pn): 996 """ 997 Return the name under which to group. This is defined as: 998 999 1. if group name is defined, use that 1000 2. if group name is defined for the primary surname, use that 1001 3. use primary surname itself otherwise 1002 1003 :param pn: raw unserialized data of name 1004 :type pn: tuple 1005 :returns: Returns the groupname string representation 1006 :rtype: str 1007 """ 1008 if pn[_GROUP]: 1009 return pn[_GROUP] 1010 return db.get_name_group_mapping(_raw_primary_surname_only( 1011 pn[_SURNAME_LIST])) 1012 1013 def _make_fn(self, format_str, d, args): 1014 """ 1015 Create the name display function and handles dependent 1016 punctuation. 1017 """ 1018 # d is a dict: dict[code] = (expr, word, translated word) 1019 1020 # First, go through and do internationalization-based 1021 # key-word replacement. Just replace ikeywords with 1022 # %codes (ie, replace "irstnamefay" with "%f", and 1023 # "IRSTNAMEFAY" for %F) 1024 1025 if (len(format_str) > 2 and 1026 format_str[0] == format_str[-1] == '"'): 1027 pass 1028 else: 1029 d_keys = [(code, _tuple[2]) for code, _tuple in d.items()] 1030 d_keys.sort(key=_make_cmp_key, reverse=True) # reverse on length and by ikeyword 1031 for (code, ikeyword) in d_keys: 1032 exp, keyword, ikeyword = d[code] 1033 format_str = format_str.replace(ikeyword, "%"+ code) 1034 format_str = format_str.replace(ikeyword.title(), "%"+ code) 1035 format_str = format_str.replace(ikeyword.upper(), "%"+ code.upper()) 1036 # Next, go through and do key-word replacement. 1037 # Just replace keywords with 1038 # %codes (ie, replace "firstname" with "%f", and 1039 # "FIRSTNAME" for %F) 1040 if (len(format_str) > 2 and 1041 format_str[0] == format_str[-1] == '"'): 1042 pass 1043 else: 1044 d_keys = [(code, _tuple[1]) for code, _tuple in d.items()] 1045 d_keys.sort(key=_make_cmp_key, reverse=True) # reverse sort on length and by keyword 1046 # if in double quotes, just use % codes 1047 for (code, keyword) in d_keys: 1048 exp, keyword, ikeyword = d[code] 1049 format_str = format_str.replace(keyword, "%"+ code) 1050 format_str = format_str.replace(keyword.title(), "%"+ code) 1051 format_str = format_str.replace(keyword.upper(), "%"+ code.upper()) 1052 # Get lower and upper versions of codes: 1053 codes = list(d.keys()) + [c.upper() for c in d] 1054 # Next, list out the matching patterns: 1055 # If it starts with "!" however, treat the punctuation verbatim: 1056 if len(format_str) > 0 and format_str[0] == "!": 1057 patterns = ["%(" + ("|".join(codes)) + ")", # %s 1058 ] 1059 format_str = format_str[1:] 1060 else: 1061 patterns = [ 1062 ",\\W*\"%(" + ("|".join(codes)) + ")\"", # ,\W*"%s" 1063 ",\\W*\\(%(" + ("|".join(codes)) + ")\\)", # ,\W*(%s) 1064 ",\\W*%(" + ("|".join(codes)) + ")", # ,\W*%s 1065 "\"%(" + ("|".join(codes)) + ")\"", # "%s" 1066 "_%(" + ("|".join(codes)) + ")_", # _%s_ 1067 "\\(%(" + ("|".join(codes)) + ")\\)", # (%s) 1068 "%(" + ("|".join(codes)) + ")", # %s 1069 ] 1070 new_fmt = format_str 1071 1072 # replace the specific format string flags with a 1073 # flag that works in standard python format strings. 1074 new_fmt = re.sub("|".join(patterns), "%s", new_fmt) 1075 1076 # replace special meaning codes we need to have verbatim in output 1077 if (len(new_fmt) > 2 and new_fmt[0] == new_fmt[-1] == '"'): 1078 new_fmt = new_fmt.replace('\\', r'\\') 1079 new_fmt = new_fmt[1:-1].replace('"', r'\"') 1080 else: 1081 new_fmt = new_fmt.replace('\\', r'\\') 1082 new_fmt = new_fmt.replace('"', '\\\"') 1083 1084 # find each format flag in the original format string 1085 # for each one we find the variable name that is needed to 1086 # replace it and add this to a list. This list will be used to 1087 # generate the replacement tuple. 1088 1089 # This compiled pattern should match all of the format codes. 1090 pat = re.compile("|".join(patterns)) 1091 param = () 1092 mat = pat.search(format_str) 1093 while mat: 1094 match_pattern = mat.group(0) # the matching pattern 1095 # prefix, code, suffix: 1096 p, code, s = re.split("%(.)", match_pattern) 1097 if code in '0123456789': 1098 code = code + s[0] 1099 s = s[1:] 1100 field = d[code.lower()][0] 1101 if code.isupper(): 1102 field += ".upper()" 1103 if p == '' and s == '': 1104 param = param + (field,) 1105 else: 1106 param = param + ("ifNotEmpty(%s,'%s','%s')" % (field, p, s), ) 1107 mat = pat.search(format_str, mat.end()) 1108 s = """ 1109def fn(%s): 1110 def ifNotEmpty(str,p,s): 1111 if str == '': 1112 return '' 1113 else: 1114 return p + str + s 1115 return cleanup_name("%s" %% (%s))""" % (args, new_fmt, ",".join(param)) 1116 try: 1117 exec(s) in globals(), locals() 1118 return locals()['fn'] 1119 except: 1120 LOG.error("\n" + 'Wrong name format string %s' % new_fmt 1121 +"\n" + ("ERROR, Edit Name format in Preferences->Display to correct") 1122 +"\n" + _('Wrong name format string %s') % new_fmt 1123 +"\n" + ("ERROR, Edit Name format in Preferences->Display to correct") 1124 ) 1125 def errfn(*arg): 1126 return _("ERROR, Edit Name format in Preferences") 1127 return errfn 1128 1129displayer = NameDisplay() 1130