1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2007 Donald N. Allingham 5# Copyright (C) 2010 Peter G. Landgren 6# Copyright (C) 2010 Craig J. Anderson 7# Copyright (C) 2014 Paul Franklin 8# 9# This program is free software; you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation; either version 2 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program; if not, write to the Free Software 21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22# 23 24""" 25Provide the SubstKeywords class that will replace keywords in a passed 26string with information about the person/marriage/spouse. For example: 27 28foo = SubstKeywords(database, person_handle) 29print foo.replace_and_clean(['$n was born on $b.']) 30 31Will return a value such as: 32 33Mary Smith was born on 3/28/1923. 34""" 35 36#------------------------------------------------------------------------ 37# 38# Gramps modules 39# 40#------------------------------------------------------------------------ 41from gramps.gen.lib import EventType, PlaceType, Location 42from gramps.gen.utils.db import get_birth_or_fallback, get_death_or_fallback 43from gramps.gen.utils.location import get_main_location 44from gramps.gen.display.place import displayer as _pd 45from gramps.gen.const import GRAMPS_LOCALE as glocale 46 47 48#------------------------------------------------------------------------ 49# 50# Local constants 51# 52#------------------------------------------------------------------------ 53class TextTypes: 54 """Four enumerations that are used to for the four main parts of a string. 55 56 and used for states. Separator is not used in states. 57 text -> remove or display 58 remove -> display 59 """ 60 separator, text, remove, display = list(range(4)) 61TXT = TextTypes() 62 63 64#------------------------------------------------------------------------ 65# 66# Formatting classes 67# 68#------------------------------------------------------------------------ 69class GenericFormat: 70 """A Generic parsing class. Will be subclassed by specific format strings 71 """ 72 73 def __init__(self, string_in, qlocale=glocale): 74 self.string_in = string_in 75 self._locale = qlocale 76 77 def _default_format(self, item): 78 """ The default format if there is no format string """ 79 pass 80 81 def is_blank(self, item): 82 """ if the information is not known (item is None), remove the format 83 string information from the input string if any. 84 """ 85 if item is None: 86 self.string_in.remove_start_end("(", ")") 87 return True 88 return False 89 90 def generic_format(self, item, code, uppr, function): 91 """the main parsing engine. 92 93 Needed are the following: the input string 94 code - List of one character (string) codes (all lowercase) 95 uppr - list of one character (string) codes that can be uppercased 96 each needs to have a lowercase equivalent in code 97 function - list of functions. 98 there is a one to one relationship with character codes and functions. 99 """ 100 if self.string_in.this != "(": 101 return self._default_format(item) 102 self.string_in.step() 103 104 main = VarString() 105 separator = SeparatorParse(self.string_in) 106 #code given in args 107 #function given in args 108 109 while self.string_in.this and self.string_in.this != ")": 110 #Check to see if _in.this is in code 111 to_upper = False 112 if uppr.find(self.string_in.this) != -1: 113 #and the result should be uppercased. 114 to_upper = True 115 where = code.find(self.string_in.this.lower()) 116 else: 117 where = code.find(self.string_in.this) 118 if where != -1: 119 self.string_in.step() 120 tmp = function[where]() 121 if to_upper: 122 tmp = tmp.upper() 123 if tmp == "" or tmp is None: 124 main.add_remove() 125 elif isinstance(tmp, VarString): # events cause this 126 main.extend(tmp) 127 else: 128 main.add_variable(tmp) 129 elif separator.is_a(): 130 main.add_separator(separator.parse_format()) 131 else: 132 main.add_text(self.string_in.parse_format()) 133 134 if self.string_in.this == ")": 135 self.string_in.step() 136 137 return main 138 139 140#------------------------------------------------------------------------ 141# Name Format strings 142#------------------------------------------------------------------------ 143class NameFormat(GenericFormat): 144 """ The name format class. 145 If no format string, the name is displayed as per preference options 146 otherwise, parse through a format string and put the name parts in 147 """ 148 149 def __init__(self, _in, locale, name_displayer): 150 GenericFormat.__init__(self, _in, locale) 151 self._nd = name_displayer 152 153 def get_name(self, person, aka): 154 """ A helper method for retrieving the person's name """ 155 name = None 156 if person: 157 if aka is None: 158 name = person.get_primary_name() 159 else: 160 for names in person.get_alternate_names(): 161 if names.get_type() == aka: 162 name = names 163 break 164 return name 165 166 def _default_format(self, name): 167 """ display the name as set in preferences """ 168 return self._nd.sorted_name(name) 169 170 def parse_format(self, name): 171 """ Parse the name """ 172 if self.is_blank(name): 173 return 174 175 def common(): 176 """ return the common name of the person """ 177 return (name.get_call_name() or 178 name.get_first_name().split(' ')[0]) 179 180 code = "tfcnxslg" 181 upper = code.upper() 182 function = [name.get_title, # t 183 name.get_first_name, # f 184 name.get_call_name, # c 185 name.get_nick_name, # n 186 common, # x 187 name.get_suffix, # s 188 name.get_surname, # l 189 name.get_family_nick_name # g 190 ] 191 192 return self.generic_format(name, code, upper, function) 193 194 195#------------------------------------------------------------------------ 196# Date Format strings 197#------------------------------------------------------------------------ 198class DateFormat(GenericFormat): 199 """ The date format class. 200 If no format string, the date is displayed as per preference options 201 otherwise, parse through a format string and put the date parts in 202 """ 203 204 def get_date(self, event): 205 """ A helper method for retrieving a date from an event """ 206 if event: 207 return event.get_date_object() 208 return None 209 210 def _default_format(self, date): 211 return self._locale.date_displayer.display(date) 212 213 def __count_chars(self, char, max_amount): 214 """ count the year/month/day codes """ 215 count = 1 # already have seen/passed one 216 while count < max_amount and self.string_in.this == char: 217 self.string_in.step() 218 count = count +1 219 return count 220 221 def parse_format(self, date): 222 """ Parse the name """ 223 224 if self.is_blank(date): 225 return 226 227 def year(year, count): 228 """ The year part only """ 229 year = str(year) 230 if year == "0": 231 return 232 233 if count == 1: # found 'y' 234 if len(year) == 1: 235 return year 236 elif year[-2] == "0": 237 return year[-1] 238 else: 239 return year[-2:] 240 elif count == 2: # found 'yy' 241 tmp = "0" + year 242 return tmp[-2:] 243 elif count == 3: # found 'yyy' 244 if len(year) > 2: 245 return year 246 else: 247 tmp = "00" + year 248 return tmp[-3:] 249 else: # count == 4 # found 'yyyy' 250 tmp = "000" + year 251 return tmp[-4:] 252 253 def month(month, count): 254 """ The month part only """ 255 month = str(month) 256 if month == "0": 257 return 258 259 if count == 1: 260 return month 261 elif count == 2: # found 'mm' 262 tmp = "0" + month 263 return tmp[-2:] 264 elif count == 3: # found 'mmm' 265 return self._locale.date_displayer.short_months[int(month)] 266 else: # found 'mmmm' 267 return self._locale.date_displayer.long_months[int(month)] 268 269 def day(day, count): 270 """ The day part only """ 271 day = str(day) 272 if day == "0": # 0 means not defined! 273 return 274 275 if count == 1: # found 'd' 276 return day 277 else: # found 'dd' 278 tmp = "0" + day 279 return tmp[-2:] 280 281 def text(): 282 return date.get_text() 283 284 def s_year(): 285 return year(date.get_year(), self.__count_chars("y", 4)) 286 287 def s_month(): 288 return month(date.get_month(), self.__count_chars("m", 4)) 289 290 def su_month(): 291 return month(date.get_month(), self.__count_chars("M", 4)).upper() 292 293 def s_day(): 294 return day(date.get_day(), self.__count_chars("d", 2)) 295 296 def e_year(): 297 return year(date.get_stop_year(), self.__count_chars("z", 4)) 298 299 def e_month(): 300 return month(date.get_stop_month(), self.__count_chars("n", 4)) 301 302 def eu_month(): 303 return month(date.get_stop_month(), 304 self.__count_chars("N", 4)).upper() 305 306 def e_day(): 307 return day(date.get_stop_day(), self.__count_chars("e", 2)) 308 309 def modifier(): 310 #ui_mods taken from date.py def lookup_modifier(self, modifier): 311 # trans_text is a defined keyword (in po/update_po.py, po/genpot.sh) 312 trans_text = self._locale.translation.gettext 313 ui_mods = ["", trans_text("before"), trans_text("after"), 314 trans_text("about"), "", "", ""] 315 return ui_mods[date.get_modifier()] 316 317 318 code = "ymMd" + "znNe" + "ot" 319 upper = "OT" 320 function = [s_year, s_month, su_month, s_day, 321 e_year, e_month, eu_month, e_day, 322 modifier, text] 323 324 return self.generic_format(date, code, upper, function) 325 326 327#------------------------------------------------------------------------ 328# Place Format strings 329#------------------------------------------------------------------------ 330class PlaceFormat(GenericFormat): 331 """ The place format class. 332 If no format string, the place is displayed as per preference options 333 otherwise, parse through a format string and put the place parts in 334 """ 335 336 def __init__(self, database, _in): 337 self.database = database 338 GenericFormat.__init__(self, _in) 339 340 def get_place(self, database, event): 341 """ A helper method for retrieving a place from an event """ 342 if event: 343 bplace_handle = event.get_place_handle() 344 if bplace_handle: 345 return database.get_place_from_handle(bplace_handle) 346 return None 347 348 def _default_format(self, place): 349 return _pd.display(self.database, place) 350 351 def parse_format(self, database, place): 352 """ Parse the place """ 353 354 if self.is_blank(place): 355 return 356 357 code = "elcuspn" + "oitxy" 358 upper = code.upper() 359 360 main_loc = get_main_location(database, place) 361 location = Location() 362 location.set_street(main_loc.get(PlaceType.STREET, '')) 363 location.set_locality(main_loc.get(PlaceType.LOCALITY, '')) 364 location.set_parish(main_loc.get(PlaceType.PARISH, '')) 365 location.set_city(main_loc.get(PlaceType.CITY, '')) 366 location.set_county(main_loc.get(PlaceType.COUNTY, '')) 367 location.set_state(main_loc.get(PlaceType.STATE, '')) 368 location.set_postal_code(main_loc.get(PlaceType.STREET, '')) 369 location.set_country(main_loc.get(PlaceType.COUNTRY, '')) 370 371 function = [location.get_street, 372 location.get_locality, 373 location.get_city, 374 location.get_county, 375 location.get_state, 376 place.get_code, 377 location.get_country, 378 379 location.get_phone, 380 location.get_parish, 381 place.get_title, 382 place.get_longitude, 383 place.get_latitude 384 ] 385 386 return self.generic_format(place, code, upper, function) 387 388 389#------------------------------------------------------------------------ 390# Event Format strings 391#------------------------------------------------------------------------ 392class EventFormat(GenericFormat): 393 """ The event format class. 394 If no format string, the event description is displayed 395 otherwise, parse through the format string and put in the parts 396 dates and places can have their own format strings 397 """ 398 399 def __init__(self, database, _in, locale): 400 self.database = database 401 GenericFormat.__init__(self, _in, locale) 402 403 def _default_format(self, event): 404 if event is None: 405 return 406 else: 407 return event.get_description() 408 409 def __empty_format(self): 410 """ clear out a sub format string """ 411 self.string_in.remove_start_end("(", ")") 412 return 413 414 def __empty_attrib(self): 415 """ clear out an attribute name """ 416 self.string_in.remove_start_end("[", "]") 417 return 418 419 def parse_format(self, event): 420 """ Parse the event format string. 421 let the date or place classes handle any sub-format strings """ 422 423 if self.is_blank(event): 424 return 425 426 def format_date(): 427 """ start formatting a date in this event """ 428 date_format = DateFormat(self.string_in, self._locale) 429 return date_format.parse_format(date_format.get_date(event)) 430 431 def format_place(): 432 """ start formatting a place in this event """ 433 place_format = PlaceFormat(self.database, self.string_in) 434 place = place_format.get_place(self.database, event) 435 return place_format.parse_format(self.database, place) 436 437 def format_attrib(): 438 """ Get the name and then get the attributes value """ 439 #Event's Atribute 440 attrib_parse = AttributeParse(self.string_in) 441 #self.string_in.step() 442 name = attrib_parse.get_name() 443 if name: 444 return attrib_parse.get_attribute(event.get_attribute_list(), 445 name) 446 else: 447 return 448 449 code = "ndDia" 450 upper = "" 451 function = [event.get_description, 452 format_date, 453 format_place, 454 event.get_gramps_id, 455 format_attrib 456 ] 457 458 return self.generic_format(event, code, upper, function) 459 460 def parse_empty(self): 461 """ remove the format string """ 462 463 code = "dDa" 464 function = [self.__empty_format, self.__empty_format, 465 self.__empty_attrib] 466 467 return self.generic_format(None, code, "", function) 468 469 470#------------------------------------------------------------------------ 471# gramps info Format strings 472#------------------------------------------------------------------------ 473class GrampsFormat: 474 """ The Gramps Info Format class. 475 This only polls information from system information. 476 """ 477 478 def __init__(self, _in, _db): 479 self.string_in = _in 480 self.db = _db 481 482 def parse_format(self): 483 """ Parse the Gramps format string. 484 let the date or place classes handle any sub-format strings """ 485 from gramps.version import VERSION 486 487 from gramps.gen.utils.config import get_researcher 488 owner = get_researcher() 489 490 code = "vtd" + "elcspn" + "om" 491 info = [VERSION, 492 owner.get_name(), 493 self.db.get_dbname(), 494 495 owner.get_address(), 496 owner.get_locality(), 497 owner.get_city(), 498 owner.get_state(), 499 owner.get_postal_code(), 500 owner.get_country(), 501 502 owner.get_phone(), 503 owner.get_email() 504 ] 505 506 where = code.find(self.string_in.this) 507 if where != -1: 508 self.string_in.step() 509 return info[where] 510 return "$G" 511 512 513#------------------------------------------------------------------------ 514# Gallery Format strings 515#------------------------------------------------------------------------ 516class GalleryFormat(GenericFormat): 517 """ The gallery format class. 518 If no format string, the photo description is displayed 519 otherwise, parse through the format string and put in the parts 520 dates (no places) can have their own format strings 521 """ 522 523 def __init__(self, database, _in, locale): 524 self.database = database 525 GenericFormat.__init__(self, _in, locale) 526 527 def _default_format(self, photo): 528 if photo is None: 529 return 530 else: 531 return photo.get_description() 532 533 def __empty_format(self): 534 """ clear out a sub format string """ 535 self.string_in.remove_start_end("(", ")") 536 return 537 538 def __empty_attrib(self): 539 """ clear out an attribute name """ 540 self.string_in.remove_start_end("[", "]") 541 return 542 543 def parse_format(self, photo): 544 """ Parse the photo format string. 545 let the date or place classes handle any sub-format strings """ 546 547 if self.is_blank(photo): 548 return 549 550 def format_date(): 551 """ start formatting a date in this photo """ 552 date_format = DateFormat(self.string_in, self._locale) 553 return date_format.parse_format(date_format.get_date(photo)) 554 555 def format_attrib(): 556 """ Get the name and then get the attributes value """ 557 #photo's Atribute 558 attrib_parse = AttributeParse(self.string_in) 559 name = attrib_parse.get_name() 560 if name: 561 return attrib_parse.get_attribute(photo.get_attribute_list(), 562 name) 563 else: 564 return 565 566 code = "ndia" 567 upper = "" 568 function = [photo.get_description, 569 format_date, 570 photo.get_gramps_id, 571 format_attrib 572 ] 573 574 return self.generic_format(photo, code, upper, function) 575 576 def parse_empty(self): 577 """ remove the format string """ 578 579 code = "da" 580 function = [self.__empty_format, self.__empty_attrib] 581 582 return self.generic_format(None, code, "", function) 583 584 585#------------------------------------------------------------------------ 586# 587# ConsumableString - The Input string class 588# 589#------------------------------------------------------------------------ 590class ConsumableString: 591 """ 592 A simple string implementation with extras to help with parsing. 593 594 This will contain the string to be parsed. or string in. 595 There will only be one of these for each processed line. 596 """ 597 def __init__(self, string): 598 self.__this_string = string 599 self.__setup() 600 601 def __setup(self): 602 """ update class attributes this and next """ 603 if len(self.__this_string) > 0: 604 self.this = self.__this_string[0] 605 else: 606 self.this = None 607 if len(self.__this_string) > 1: 608 self.next = self.__this_string[1] 609 else: 610 self.next = None 611 612 def step(self): 613 """ remove the first char from the string """ 614 self.__this_string = self.__this_string[1:] 615 self.__setup() 616 return self.this 617 618 def step2(self): 619 """ remove the first two chars from the string """ 620 self.__this_string = self.__this_string[2:] 621 self.__setup() 622 return self.this 623 624 def remove_start_end(self, start, end): 625 """ Removes a start, end block from the string if there """ 626 if self.this == start: 627 self.text_to_next(end) 628 629 def __get_a_char_of_text(self): 630 """ Removes one char of TEXT from the string and returns it. """ 631 if self.this == "\\": 632 if self.next is None: 633 rtrn = "\\" 634 else: 635 rtrn = self.next 636 self.step2() 637 else: 638 rtrn = self.this 639 self.step() 640 return rtrn 641 642 def text_to_next(self, char): 643 """ return/remove a format strings from here """ 644 new_str = "" 645 while self.this is not None and self.this != char: 646 new_str += self.__get_a_char_of_text() 647 if self.this == char: 648 self.step() 649 return new_str 650 651 def is_a(self): 652 return True 653 654 def parse_format(self): 655 rtrn = self.__get_a_char_of_text() 656 657 if rtrn: 658 return rtrn 659 return '' 660 661 662#------------------------------------------------------------------------ 663# 664# VarString class - The Output string class 665# 666#------------------------------------------------------------------------ 667class VarString: 668 """ 669 The current state of the entire string (integer from TextTypes) 670 A list to hold tuple object (integer from TextTypes, string) 671 672 This will contain the string that will be displayed. or string out. 673 it is used for groups and format strings. 674 """ 675 def __init__(self, start_state=TXT.remove): 676 self.state = start_state # overall state of the string. 677 self._text = [] # list of tuples (TXT.?, string) 678 679 def __update_state(self, new_status): 680 if new_status > self.state: 681 self.state = new_status 682 683 def add_text(self, text): 684 self._text.append((TXT.text, text)) 685 686 def add_variable(self, text): 687 self.state = TXT.display 688 self._text.append((TXT.text, text)) 689 690 def add_remove(self): 691 self.__update_state(TXT.remove) 692 self._text.append((TXT.remove, "")) 693 694 def add_separator(self, text): 695 self._text.append((TXT.separator, text)) 696 697 def get_final(self): 698 #if self.state == TXT.remove: 699 # return (TXT.remove, "") 700 701 curr_string = "" 702 index = 0 703 704 while index < len(self._text): 705 706 if self._text[index][0] == TXT.text: 707 curr_string += self._text[index][1] 708 index = index + 1 709 continue # while self._text: 710 if index +1 == len(self._text): 711 if self._text[index][0] == TXT.separator and curr_string != '': 712 curr_string += self._text[index][1] 713 index = index + 1 714 break # while self._text: 715 716 type_0_1 = (self._text[index][0], self._text[index+1][0]) 717 718 #if type_0_1 == (TXT.remove, TXT.remove): 719 # pass 720 if type_0_1 == (TXT.remove, TXT.separator): 721 index = index + 1 722 #elif type_0_1 == (TXT.remove, TXT.text): 723 # pass 724 elif type_0_1 == (TXT.separator, TXT.remove): 725 index = index + 1 726 #elif type_0_1 == (TXT.separator, TXT.separator): 727 # pass 728 elif type_0_1 == (TXT.separator, TXT.text): 729 curr_string += self._text[index][1] 730 #else: 731 # print "#oops Should never get here." 732 index = index + 1 733 734 #return what we have 735 return (self.state, curr_string) 736 #print("===" + str(self.state) + " '" + str(curr_string) + "'") 737 738 def extend(self, acquisition): 739 """ 740 acquisition is a VarString object 741 Merge the content of acquisition into this place. 742 """ 743 self.__update_state(acquisition.state) 744 745 if acquisition.state != TXT.display: 746 #The sub {} was TXT.remove. We don't want to simply ignore it. 747 self.add_remove() # add a remove que here to note it. 748 return 749 750 self._text.extend(acquisition._text) 751 752 753#------------------------------------------------------------------------ 754# 755# Parsers 756# 757#------------------------------------------------------------------------ 758#------------------------------------------------------------------------ 759# SeparatorParse 760#------------------------------------------------------------------------ 761class SeparatorParse: 762 """ parse out a separator """ 763 def __init__(self, consumer_in): 764 self._in = consumer_in 765 766 def is_a(self): 767 return self._in.this == "<" 768 769 def parse_format(self): 770 if not self.is_a(): 771 return 772 """ get the text and return it """ 773 self._in.step() 774 return self._in.text_to_next(">") 775 776#------------------------------------------------------------------------ 777# AttributeParse 778#------------------------------------------------------------------------ 779class AttributeParse: 780 """ Parse attributes """ 781 782 def __init__(self, consumer_in): 783 self._in = consumer_in 784 785 def get_name(self): 786 """ Gets a name inside a [] block """ 787 if self._in.this != "[": 788 return 789 self._in.step() 790 return self._in.text_to_next("]") 791 792 def get_attribute(self, attrib_list, attrib_name): 793 """ Get an attribute by name """ 794 if attrib_name == "": 795 return 796 for attr in attrib_list: 797 if str(attr.get_type()) == attrib_name: 798 return str(attr.get_value()) 799 return 800 801 def is_a(self): 802 """ check """ 803 return self._in.this == "a" 804 805 def parse_format(self, attrib_list): 806 """ Get the attribute and add it to the string out """ 807 name = self.get_name() 808 return self.get_attribute(attrib_list, name) 809 810#------------------------------------------------------------------------ 811# VariableParse 812#------------------------------------------------------------------------ 813class VariableParse: 814 """ Parse the individual variables """ 815 816 def __init__(self, friend, database, consumer_in, locale, name_displayer): 817 self.friend = friend 818 self.database = database 819 self._in = consumer_in 820 self._locale = locale 821 self._nd = name_displayer 822 823 def is_a(self): 824 """ check """ 825 return self._in.this == "$" and self._in.next is not None and \ 826 "nsijbBdDmMvVauetTpPG".find(self._in.next) != -1 827 828 def get_event_by_type(self, marriage, e_type): 829 """ get an event from a type """ 830 if marriage is None: 831 return None 832 for e_ref in marriage.get_event_ref_list(): 833 if not e_ref: 834 continue 835 event = self.friend.database.get_event_from_handle(e_ref.ref) 836 if event.get_type() == e_type: 837 return event 838 return None 839 840 def get_event_by_name(self, person, event_name): 841 """ get an event from a name. """ 842 if not person: 843 return None 844 for e_ref in person.get_event_ref_list(): 845 if not e_ref: 846 continue 847 event = self.friend.database.get_event_from_handle(e_ref.ref) 848 if str(event.get_type()) == event_name: 849 return event 850 return None 851 852 def empty_item(self, item): 853 """ return false if there is a valid item(date or place). 854 Otherwise 855 add a TXT.remove marker in the output string 856 remove any format strings from the input string 857 """ 858 if item is not None: 859 return False 860 861 self._in.remove_start_end("(", ")") 862 return True 863 864 def empty_attribute(self, person): 865 """ return false if there is a valid person. 866 Otherwise 867 add a TXT.remove marker in the output string 868 remove any attribute name from the input string 869 """ 870 if person: 871 return False 872 873 self._in.remove_start_end("[", "]") 874 return True 875 876 def __parse_date(self, event): 877 """ sub to process a date 878 Given an event, get the date object, process the format, 879 return the result """ 880 date_f = DateFormat(self._in, self._locale) 881 date = date_f.get_date(event) 882 if self.empty_item(date): 883 return 884 return date_f.parse_format(date) 885 886 def __parse_place(self, event): 887 """ sub to process a date 888 Given an event, get the place object, process the format, 889 return the result """ 890 place_f = PlaceFormat(self.database, self._in) 891 place = place_f.get_place(self.database, event) 892 if self.empty_item(place): 893 return 894 return place_f.parse_format(self.database, place) 895 896 def __parse_name(self, person, attrib_parse): 897 name_format = NameFormat(self._in, self._locale, self._nd) 898 name = name_format.get_name(person, attrib_parse.get_name()) 899 return name_format.parse_format(name) 900 901 def __parse_id(self, first_class_object): 902 if first_class_object is not None: 903 return first_class_object.get_gramps_id() 904 else: 905 return 906 907 def __parse_event(self, person, attrib_parse): 908 event = self.get_event_by_name(person, attrib_parse.get_name()) 909 event_f = EventFormat(self.database, self._in, self._locale) 910 if event: 911 return event_f.parse_format(event) 912 else: 913 event_f.parse_empty() 914 return 915 916 def __get_photo(self, person_or_marriage): 917 """ returns the first photo in the media list or None """ 918 media_list = person_or_marriage.get_media_list() 919 for media_ref in media_list: 920 media_handle = media_ref.get_reference_handle() 921 media = self.database.get_media_from_handle(media_handle) 922 mime_type = media.get_mime_type() 923 if mime_type and mime_type.startswith("image"): 924 return media 925 return None 926 927 def __parse_photo(self, person_or_marriage): 928 photo_f = GalleryFormat(self.database, self._in, self._locale) 929 if person_or_marriage is None: 930 return photo_f.parse_empty() 931 photo = self.__get_photo(person_or_marriage) 932 if photo: 933 return photo_f.parse_format(photo) 934 else: 935 return photo_f.parse_empty() 936 937 def parse_format(self): 938 """Parse the $ variables. """ 939 if not self.is_a(): 940 return 941 942 attrib_parse = AttributeParse(self._in) 943 next_char = self._in.next 944 self._in.step2() 945 946 if next_char == "n": 947 #Person's name 948 return self.__parse_name(self.friend.person, attrib_parse) 949 elif next_char == "s": 950 #Souses name 951 return self.__parse_name(self.friend.spouse, attrib_parse) 952 953 elif next_char == "i": 954 #Person's Id 955 return self.__parse_id(self.friend.person) 956 elif next_char == "j": 957 #Marriage Id 958 return self.__parse_id(self.friend.family) 959 960 elif next_char == "b": 961 #Person's Birth date 962 if self.empty_item(self.friend.person): 963 return 964 return self.__parse_date( 965 get_birth_or_fallback(self.friend.database, self.friend.person)) 966 elif next_char == "d": 967 #Person's Death date 968 if self.empty_item(self.friend.person): 969 return 970 return self.__parse_date( 971 get_death_or_fallback(self.friend.database, self.friend.person)) 972 elif next_char == "m": 973 #Marriage date 974 if self.empty_item(self.friend.family): 975 return 976 return self.__parse_date( 977 self.get_event_by_type(self.friend.family, 978 EventType.MARRIAGE)) 979 elif next_char == "v": 980 #Divorce date 981 if self.empty_item(self.friend.family): 982 return 983 return self.__parse_date( 984 self.get_event_by_type(self.friend.family, 985 EventType.DIVORCE)) 986 elif next_char == "T": 987 #Todays date 988 date_f = DateFormat(self._in) 989 from gramps.gen.lib.date import Today 990 date = Today() 991 if self.empty_item(date): 992 return 993 return date_f.parse_format(date) 994 995 elif next_char == "B": 996 #Person's birth place 997 if self.empty_item(self.friend.person): 998 return 999 return self.__parse_place( 1000 get_birth_or_fallback(self.friend.database, self.friend.person)) 1001 elif next_char == "D": 1002 #Person's death place 1003 if self.empty_item(self.friend.person): 1004 return 1005 return self.__parse_place( 1006 get_death_or_fallback(self.friend.database, self.friend.person)) 1007 elif next_char == "M": 1008 #Marriage place 1009 if self.empty_item(self.friend.family): 1010 return 1011 return self.__parse_place( 1012 self.get_event_by_type(self.friend.family, 1013 EventType.MARRIAGE)) 1014 elif next_char == "V": 1015 #Divorce place 1016 if self.empty_item(self.friend.family): 1017 return 1018 return self.__parse_place( 1019 self.get_event_by_type(self.friend.family, 1020 EventType.DIVORCE)) 1021 1022 elif next_char == "a": 1023 #Person's Atribute 1024 if self.empty_attribute(self.friend.person): 1025 return 1026 return attrib_parse.parse_format( 1027 self.friend.person.get_attribute_list()) 1028 elif next_char == "u": 1029 #Marriage Atribute 1030 if self.empty_attribute(self.friend.family): 1031 return 1032 return attrib_parse.parse_format( 1033 self.friend.family.get_attribute_list()) 1034 1035 elif next_char == "e": 1036 #person event 1037 return self.__parse_event(self.friend.person, attrib_parse) 1038 elif next_char == "t": 1039 #family event 1040 return self.__parse_event(self.friend.family, attrib_parse) 1041 1042 elif next_char == 'p': 1043 #photo for the person 1044 return self.__parse_photo(self.friend.person) 1045 elif next_char == 'P': 1046 #photo for the marriage 1047 return self.__parse_photo(self.friend.family) 1048 1049 elif next_char == "G": 1050 gramps_format = GrampsFormat(self._in, self.database) 1051 return gramps_format.parse_format() 1052 1053 1054#------------------------------------------------------------------------ 1055# 1056# SubstKeywords 1057# 1058#------------------------------------------------------------------------ 1059class SubstKeywords: 1060 """Accepts a person/family with format lines and returns a new set of lines 1061 using variable substitution to make it. 1062 1063 The individual variables are defined with the classes that look for them. 1064 1065 Needed: 1066 Database object 1067 person_handle 1068 This will be the center person for the display 1069 family_handle 1070 this will specify the specific family/spouse to work with. 1071 If none given, then the first/preferred family/spouse is used 1072 """ 1073 def __init__(self, database, locale, name_displayer, 1074 person_handle, family_handle=None): 1075 """get the person and find the family/spouse to use for this display""" 1076 1077 self.database = database 1078 self.family = None 1079 self.spouse = None 1080 self.line = None # Consumable_string - set below 1081 self._locale = locale 1082 self._nd = name_displayer 1083 1084 self.person = None 1085 if person_handle is not None: 1086 self.person = database.get_person_from_handle(person_handle) 1087 if self.person is None: 1088 return 1089 1090 fam_hand_list = self.person.get_family_handle_list() 1091 if fam_hand_list: 1092 if family_handle in fam_hand_list: 1093 self.family = database.get_family_from_handle(family_handle) 1094 else: 1095 #Error. fam_hand_list[0] below may give wrong marriage info. 1096 #only here because of OLD specifications. Specs read: 1097 # * $S/%S 1098 # Displays the name of the person's preferred ... 1099 # 'preferred' means FIRST. 1100 #The first might not be the correct marriage to display. 1101 #else: clause SHOULD be removed. 1102 self.family = database.get_family_from_handle(fam_hand_list[0]) 1103 1104 father_handle = self.family.get_father_handle() 1105 mother_handle = self.family.get_mother_handle() 1106 self.spouse = None 1107 if father_handle == person_handle: 1108 if mother_handle: 1109 self.spouse = database.get_person_from_handle(mother_handle) 1110 else: 1111 if father_handle: 1112 self.spouse = database.get_person_from_handle(father_handle) 1113 1114 def __parse_line(self): 1115 """parse each line of text and return the new displayable line 1116 1117 There are four things we can find here 1118 A {} group which will make/end as needed. 1119 A <> separator 1120 A $ variable - Handled separately 1121 or text 1122 """ 1123 stack_var = [] 1124 curr_var = VarString(TXT.text) 1125 1126 #First we are going take care of all variables/groups 1127 #break down all {} (groups) and $ (vars) into either 1128 #(TXT.text, resulting_string) or (TXT.remove, '') 1129 variable = VariableParse(self, self.database, self.line, 1130 self._locale, self._nd) 1131 1132 while self.line.this: 1133 if self.line.this == "{": 1134 #Start of a group 1135 #push what we have onto the stack 1136 stack_var.append(curr_var) 1137 #Setup 1138 curr_var = VarString() 1139 #step 1140 self.line.step() 1141 1142 elif self.line.this == "}" and len(stack_var) > 0: #End of a group 1143 #add curr to what is on the (top) stack and pop into current 1144 #or pop the stack into current and add TXT.remove 1145 direction = curr_var.state 1146 if direction == TXT.display: 1147 #add curr onto the top slot of the stack 1148 stack_var[-1].extend(curr_var) 1149 1150 #pop what we have on the stack 1151 curr_var = stack_var.pop() 1152 1153 if direction == TXT.remove: 1154 #add remove que 1155 curr_var.add_remove() 1156 #step 1157 self.line.step() 1158 1159 elif variable.is_a(): # $ (variables) 1160 rtrn = variable.parse_format() 1161 if rtrn is None: 1162 curr_var.add_remove() 1163 elif isinstance(rtrn, VarString): 1164 curr_var.extend(rtrn) 1165 else: 1166 curr_var.add_variable(rtrn) 1167 1168 elif self.line.this == "<": # separator 1169 self.line.step() 1170 curr_var.add_separator(self.line.text_to_next(">")) 1171 1172 else: #regular text 1173 curr_var.add_text(self.line.parse_format()) 1174 1175 #the stack is for groups/subgroup and may contain items 1176 #if the user does not close his/her {} 1177 #squash down the stack 1178 while stack_var: 1179 direction = curr_var.state 1180 if direction == TXT.display: 1181 #add curr onto the top slot of the stack 1182 stack_var[-1].extend(curr_var) 1183 1184 #pop what we have on the stack 1185 curr_var = stack_var.pop() 1186 1187 if direction == TXT.remove: 1188 #add remove que 1189 curr_var.add_remove() 1190 #step 1191 self.line.step() 1192 1193 #return what we have 1194 return curr_var.get_final() 1195 1196 1197 def __main_level(self): 1198 #Check only if the user wants to not display the line if TXT.remove 1199 remove_line_tag = False 1200 if self.line.this == "-": 1201 remove_line_tag = True 1202 self.line.step() 1203 1204 state, line = self.__parse_line() 1205 1206 if state is TXT.remove and remove_line_tag: 1207 return None 1208 return line 1209 1210 def replace_and_clean(self, lines): 1211 """ 1212 return a new array of lines with all of the substitutions done 1213 """ 1214 new = [] 1215 for this_line in lines: 1216 if this_line == "": 1217 new.append(this_line) 1218 continue 1219 #print "- ", this_line 1220 self.line = ConsumableString(this_line) 1221 new_line = self.__main_level() 1222 #print "+ ", new_line 1223 if new_line is not None: 1224 new.append(new_line) 1225 1226 if new == []: 1227 new = [""] 1228 return new 1229 1230 1231#Acts 20:35 (New International Version) 1232#In everything I did, I showed you that by this kind of hard work 1233#we must help the weak, remembering the words the Lord Jesus himself 1234#said: 'It is more blessed to give than to receive.' 1235 1236 1237if __name__ == '__main__': 1238#------------------------------------------------------------------------- 1239# 1240# For Testing everything except VariableParse, SubstKeywords and EventFormat 1241# apply it as a script: 1242# 1243# ==> in command line do "PYTHONPATH=??? python libsubstkeyword.py" 1244# 1245# You will need to put in your own path to the src directory 1246# 1247#------------------------------------------------------------------------- 1248 # pylint: disable=C0103 1249 1250 def combinations(c, r): 1251 # combinations('ABCD', 2) --> AB AC AD BC BD CD 1252 # combinations(range(4), 3) --> 012 013 023 123 1253 pool = tuple(range(c)) 1254 n = len(pool) 1255 if r > n: 1256 return 1257 indices = list(range(r)) 1258 yield tuple(pool[i] for i in indices) 1259 while True: 1260 for i in reversed(list(range(r))): 1261 if indices[i] != i + n - r: 1262 break 1263 else: 1264 return 1265 indices[i] += 1 1266 for j in range(i+1, r): 1267 indices[j] = indices[j-1] + 1 1268 yield tuple(pool[i] for i in indices) 1269 1270 def main_level_test(_in, testing_class, testing_what): 1271 """This is a mini def __main_level(self): 1272 """ 1273 main = _in 1274 sepa = SeparatorParse(_in) 1275 test = testing_class(_in) 1276 1277 while _in.this: 1278 if main.is_a(): 1279 main.parse_format(_in) 1280 elif sepa.is_a(): 1281 sepa.parse_format(main) 1282 elif _in.this == "$": 1283 _in.step() 1284 main.add_variable( 1285 test.parse_format(testing_what)) 1286 else: 1287 _in.parse_format(main) 1288 1289 main.combine_all() 1290 1291 state, line = main.get_string() 1292 if state is TXT.remove: 1293 return None 1294 else: 1295 return line 1296 1297 1298 from gramps.gen.lib.date import Date 1299 y_or_n = () 1300 date_to_test = Date() 1301 1302 def date_set(): 1303 date_to_test.set_yr_mon_day( 1304 1970 if 0 in y_or_n else 0, 1305 9 if 1 in y_or_n else 0, 1306 3 if 2 in y_or_n else 0 1307 ) 1308 #print date_to_test 1309 1310 line_in = "<Z>$(yyy) <a>$(<Z>Mm)<b>$(mm){<c>$(d)}{<d>$(yyyy)<e>}<f>$(yy)" 1311 consume_str = ConsumableString(line_in) 1312 1313 print(line_in) 1314 print("#None are known") 1315 tmp = main_level_test(consume_str, DateFormat, date_to_test) 1316 print(tmp) 1317 print("Good" if tmp == " " else "!! bad !!") 1318 1319 1320 print() 1321 print() 1322 print("#One is known") 1323 answer = [] 1324 for y_or_n in combinations(3, 1): 1325 date_set() 1326 consume_str = ConsumableString(line_in) 1327 tmp = main_level_test(consume_str, DateFormat, date_to_test) 1328 print(tmp) 1329 answer.append(tmp) 1330 print("Good" if answer == [ 1331 "1970 d1970f70", 1332 " a99b09", 1333 " c3" 1334 ] else "!! bad !!") 1335 1336 1337 print() 1338 print() 1339 print("#Two are known") 1340 answer = [] 1341 for y_or_n in combinations(3, 2): 1342 date_set() 1343 consume_str = ConsumableString(line_in) 1344 tmp = main_level_test(consume_str, DateFormat, date_to_test) 1345 print(tmp) 1346 answer.append(tmp) 1347 print("Good" if answer == [ 1348 "1970 a99b09d1970f70", 1349 "1970 c3d1970f70", 1350 " a99b09c3" 1351 ] else "!! bad !!") 1352 1353 1354 print() 1355 print() 1356 print("#All are known") 1357 answer = [] 1358 y_or_n = (0, 1, 2) 1359 date_set() 1360 consume_str = ConsumableString(line_in) 1361 tmp = main_level_test(consume_str, DateFormat, date_to_test) 1362 print(tmp) 1363 answer.append(tmp) 1364 print("Good" if answer == [ 1365 "1970 a99b09c3d1970f70" 1366 ] else "!! bad !!") 1367 1368 import sys 1369 sys.exit() 1370 print() 1371 print() 1372 print("=============") 1373 print("=============") 1374 1375 from gramps.gen.lib.name import Name 1376 y_or_n = () 1377 name_to_test = Name() 1378 1379 def name_set(): 1380 #code = "tfcnxslg" 1381 name_to_test.set_call_name("Bob" if 0 in y_or_n else "") 1382 name_to_test.set_title("Dr." if 1 in y_or_n else "") 1383 name_to_test.set_first_name("Billy" if 2 in y_or_n else "") 1384 name_to_test.set_nick_name("Buck" if 3 in y_or_n else "") 1385 name_to_test.set_suffix("IV" if 4 in y_or_n else "") 1386 #now can we put something in for the last name? 1387 name_to_test.set_family_nick_name("The Clubs" if 5 in y_or_n else "") 1388 1389 line_in = "{$(c)$(t)<1>{<2>$(f)}{<3>$(n){<0> " 1390 line_in = line_in + "<0>}<4>$(x)}$(s)<5>$(l)<6>$(g)<0>" 1391 consume_str = ConsumableString(line_in) 1392 1393 print() 1394 print() 1395 print(line_in) 1396 print("#None are known") 1397 tmp = main_level_test(consume_str, NameFormat, name_to_test) 1398 print(tmp) 1399 print("Good" if tmp is None else "!! bad !!") 1400 1401 1402 print() 1403 print() 1404 print("#Two are known") 1405 answer = [] 1406 for y_or_n in combinations(6, 2): 1407 name_set() 1408 consume_str = ConsumableString(line_in) 1409 tmp = main_level_test(consume_str, NameFormat, name_to_test) 1410 print(tmp) 1411 answer.append(tmp) 1412 print("Good" if answer == [ 1413 "BobDr.4Bob", 1414 "Bob2Billy4Bob", 1415 "Bob3Buck4Bob", 1416 "Bob4BobIV", 1417 "Bob4BobThe Clubs", 1418 "Dr.2Billy4Billy", 1419 "Dr.3Buck", 1420 "Dr.1IV", 1421 "Dr.6The Clubs", 1422 "Billy3Buck4Billy", 1423 "Billy4BillyIV", 1424 "Billy4BillyThe Clubs", 1425 "BuckIV", 1426 "BuckThe Clubs", 1427 "IV6The Clubs" 1428 ] else "!! bad !!") 1429 1430 1431 print() 1432 print() 1433 print("#All are known") 1434 y_or_n = (0, 1, 2, 3, 4, 5) 1435 name_set() 1436 consume_str = ConsumableString(line_in) 1437 answer = main_level_test(consume_str, NameFormat, name_to_test) 1438 print(answer) 1439 print("Good" if answer == "BobDr.2Billy3Buck4BobIV6The Clubs" 1440 else "!! bad !!") 1441 1442 1443 print() 1444 print() 1445 print("=============") 1446 print("=============") 1447 1448 from gramps.gen.lib.place import Place 1449 y_or_n = () 1450 place_to_test = Place() 1451 1452 def place_set(): 1453 #code = "elcuspnitxy" 1454 main_loc = place_to_test.get_main_location() 1455 main_loc.set_street( 1456 "Lost River Ave." if 0 in y_or_n else "" 1457 ) 1458 main_loc.set_locality( 1459 "Second district" if 1 in y_or_n else "" 1460 ) 1461 main_loc.set_city( 1462 "Arco" if 2 in y_or_n else "" 1463 ) 1464 main_loc.set_county( 1465 "Butte" if 3 in y_or_n else "" 1466 ) 1467 main_loc.set_state( 1468 "Idaho" if 4 in y_or_n else "" 1469 ) 1470 main_loc.set_postal_code( 1471 "83213" if 5 in y_or_n else "" 1472 ) 1473 main_loc.set_country( 1474 "USA" if 6 in y_or_n else "" 1475 ) 1476 main_loc.set_parish( 1477 "St Anns" if 7 in y_or_n else "" 1478 ) 1479 place_to_test.set_title( 1480 "Atomic City" if 8 in y_or_n else "" 1481 ) 1482 place_to_test.set_longitude( 1483 "N43H38'5\"N" if 9 in y_or_n else "" 1484 ) 1485 place_to_test.set_latitude( 1486 "W113H18'5\"W" if 10 in y_or_n else "" 1487 ) 1488 1489 #code = "txy" 1490 line_in = "$(e)<1>{<2>$(l) <3> $(c)<4><0><5>{$(s)<6>$(p)<7>" + \ 1491 "{<1>$(n)<2>}<3>$(i<0>)<4>}<5>$(t)<6>$(x)<7>}<8>$(y)" 1492 consume_str = ConsumableString(line_in) 1493 1494 print() 1495 print() 1496 print(line_in) 1497 print("#None are known") 1498 tmp = main_level_test(consume_str, PlaceFormat, place_to_test) 1499 print(tmp) 1500 print("Good" if tmp == "" else "!! bad !!") 1501 1502 1503 print() 1504 print() 1505 print("#Three are known (string lengths only)") 1506 answer = [] 1507 for y_or_n in combinations(11, 4): 1508 place_set() 1509 consume_str = ConsumableString(line_in) 1510 tmp = main_level_test(consume_str, PlaceFormat, place_to_test) 1511 #print tmp 1512 answer.append(len(tmp)) 1513 print(answer) 1514 print("Good" if answer == [ 1515 38, 44, 44, 42, 46, 50, 49, 50, 40, 40, 38, 42, 1516 46, 45, 46, 46, 44, 48, 52, 51, 52, 44, 48, 52, 51, 52, 46, 50, 49, 50, 1517 54, 53, 54, 57, 58, 57, 28, 28, 26, 30, 34, 33, 34, 34, 32, 36, 40, 39, 1518 40, 32, 36, 40, 39, 40, 34, 38, 37, 38, 42, 41, 42, 45, 46, 45, 30, 28, 1519 32, 36, 35, 36, 28, 32, 36, 35, 36, 30, 34, 33, 34, 38, 37, 38, 41, 42, 1520 41, 34, 38, 42, 41, 42, 36, 40, 39, 40, 44, 43, 44, 47, 48, 47, 36, 40, 1521 39, 40, 44, 43, 44, 47, 48, 47, 42, 41, 42, 45, 46, 45, 49, 50, 49, 53, 1522 28, 28, 26, 30, 34, 33, 34, 34, 32, 36, 40, 39, 40, 32, 36, 40, 39, 40, 1523 34, 38, 37, 38, 42, 41, 42, 45, 46, 45, 30, 28, 32, 36, 35, 36, 28, 32, 1524 36, 35, 36, 30, 34, 33, 34, 38, 37, 38, 41, 42, 41, 34, 38, 42, 41, 42, 1525 36, 40, 39, 40, 44, 43, 44, 47, 48, 47, 36, 40, 39, 40, 44, 43, 44, 47, 1526 48, 47, 42, 41, 42, 45, 46, 45, 49, 50, 49, 53, 19, 17, 21, 25, 24, 25, 1527 17, 21, 25, 24, 25, 19, 23, 22, 23, 27, 26, 27, 30, 31, 30, 23, 27, 31, 1528 30, 31, 25, 29, 28, 29, 33, 32, 33, 36, 37, 36, 25, 29, 28, 29, 33, 32, 1529 33, 36, 37, 36, 31, 30, 31, 34, 35, 34, 38, 39, 38, 42, 19, 23, 27, 26, 1530 27, 21, 25, 24, 25, 29, 28, 29, 32, 33, 32, 21, 25, 24, 25, 29, 28, 29, 1531 32, 33, 32, 27, 26, 27, 30, 31, 30, 34, 35, 34, 38, 27, 31, 30, 31, 35, 1532 34, 35, 38, 39, 38, 33, 32, 33, 36, 37, 36, 40, 41, 40, 44, 33, 32, 33, 1533 36, 37, 36, 40, 41, 40, 44, 38, 39, 38, 42, 46] else "!! bad !!") 1534 1535