1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2007 Donald N. Allingham 5# Copyright (C) 200?-2013 Benny Malengier 6# Copyright (C) 2009 Douglas S. Blank 7# Copyright (C) 2010-2011 Nick Hall 8# Copyright (C) 2011 Michiel D. Nauta 9# Copyright (C) 2011 Tim G L Lyons 10# 11# This program is free software; you can redistribute it and/or modify 12# it under the terms of the GNU General Public License as published by 13# the Free Software Foundation; either version 2 of the License, or 14# (at your option) any later version. 15# 16# This program is distributed in the hope that it will be useful, 17# but WITHOUT ANY WARRANTY; without even the implied warranty of 18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19# GNU General Public License for more details. 20# 21# You should have received a copy of the GNU General Public License 22# along with this program; if not, write to the Free Software 23# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 24# 25 26#------------------------------------------------------------------------- 27# 28# Standard Python Modules 29# 30#------------------------------------------------------------------------- 31import os 32import sys 33import time 34from xml.parsers.expat import ExpatError, ParserCreate 35from xml.sax.saxutils import escape 36from gramps.gen.const import URL_WIKISTRING 37from gramps.gen.const import GRAMPS_LOCALE as glocale 38_ = glocale.translation.gettext 39import re 40import logging 41from collections import abc 42LOG = logging.getLogger(".ImportXML") 43 44#------------------------------------------------------------------------- 45# 46# Gramps Modules 47# 48#------------------------------------------------------------------------- 49from gramps.gen.mime import get_type 50from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef, 51 ChildRefType, Citation, Date, DateError, Event, 52 EventRef, EventRoleType, EventType, Family, LdsOrd, 53 Location, Media, MediaRef, Name, 54 NameOriginType, NameType, Note, NoteType, Person, 55 PersonRef, Place, PlaceName, PlaceRef, PlaceType, 56 RepoRef, Repository, Researcher, Source, 57 SrcAttribute, SrcAttributeType, StyledText, 58 StyledTextTag, StyledTextTagType, Surname, Tag, Url) 59from gramps.gen.db import DbTxn 60#from gramps.gen.db.write import CLASS_TO_KEY_MAP 61from gramps.gen.errors import GrampsImportError 62from gramps.gen.utils.id import create_id 63from gramps.gen.utils.db import family_name 64from gramps.gen.utils.unknown import make_unknown, create_explanation_note 65from gramps.gen.utils.file import create_checksum, media_path, expand_media_path 66from gramps.gen.datehandler import parser, set_date 67from gramps.gen.display.name import displayer as name_displayer 68from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, 69 EVENT_KEY, MEDIA_KEY, PLACE_KEY, 70 REPOSITORY_KEY, NOTE_KEY, TAG_KEY, 71 CITATION_KEY, CLASS_TO_KEY_MAP) 72from gramps.gen.updatecallback import UpdateCallback 73from gramps.version import VERSION 74from gramps.gen.config import config 75#import gramps.plugins.lib.libgrampsxml 76from gramps.plugins.lib import libgrampsxml 77from gramps.gen.plug.utils import version_str_to_tup 78from gramps.plugins.lib.libplaceimport import PlaceImport 79 80#------------------------------------------------------------------------- 81# 82# Try to detect the presence of gzip 83# 84#------------------------------------------------------------------------- 85try: 86 import gzip 87 GZIP_OK = True 88except: 89 GZIP_OK = False 90 91PERSON_RE = re.compile(r"\s*\<person\s(.*)$") 92 93CHILD_REL_MAP = { 94 "Birth" : ChildRefType(ChildRefType.BIRTH), 95 "Adopted" : ChildRefType(ChildRefType.ADOPTED), 96 "Stepchild" : ChildRefType(ChildRefType.STEPCHILD), 97 "Sponsored" : ChildRefType(ChildRefType.SPONSORED), 98 "Foster" : ChildRefType(ChildRefType.FOSTER), 99 "Unknown" : ChildRefType(ChildRefType.UNKNOWN), 100 } 101 102# feature requests 2356, 1658: avoid genitive form 103EVENT_FAMILY_STR = _("%(event_name)s of %(family)s") 104# feature requests 2356, 1658: avoid genitive form 105EVENT_PERSON_STR = _("%(event_name)s of %(person)s") 106 107HANDLE = 0 108INSTANTIATED = 1 109 110#------------------------------------------------------------------------- 111# 112# Importing data into the currently open database. 113# Must takes care of renaming media files according to their new IDs. 114# 115#------------------------------------------------------------------------- 116def importData(database, filename, user): 117 filename = os.path.normpath(filename) 118 basefile = os.path.dirname(filename) 119 database.smap = {} 120 database.pmap = {} 121 database.fmap = {} 122 line_cnt = 1 123 person_cnt = 0 124 125 with ImportOpenFileContextManager(filename, user) as xml_file: 126 if xml_file is None: 127 return 128 129 if filename == '-': 130 change = time.time() 131 else: 132 change = os.path.getmtime(filename) 133 if database.get_feature("skip-import-additions"): # don't add source or tags 134 parser = GrampsParser(database, user, change, None) 135 else: 136 parser = GrampsParser(database, user, change, 137 (config.get('preferences.tag-on-import-format') if 138 config.get('preferences.tag-on-import') else None)) 139 140 if filename != '-': 141 linecounter = LineParser(filename) 142 line_cnt = linecounter.get_count() 143 person_cnt = linecounter.get_person_count() 144 145 read_only = database.readonly 146 database.readonly = False 147 148 try: 149 info = parser.parse(xml_file, line_cnt, person_cnt) 150 except GrampsImportError as err: # version error 151 user.notify_error(*err.messages()) 152 return 153 except IOError as msg: 154 user.notify_error(_("Error reading %s") % filename, str(msg)) 155 import traceback 156 traceback.print_exc() 157 return 158 except ExpatError as msg: 159 user.notify_error(_("Error reading %s") % filename, 160 str(msg) + "\n" + 161 _("The file is probably either corrupt or not a " 162 "valid Gramps database.")) 163 return 164 165 database.readonly = read_only 166 return info 167 168 169#------------------------------------------------------------------------- 170# 171# Remove extraneous spaces 172# 173#------------------------------------------------------------------------- 174 175def rs(text): 176 return ' '.join(text.split()) 177 178def fix_spaces(text_list): 179 return '\n'.join(map(rs, text_list)) 180 181#------------------------------------------------------------------------- 182# 183# 184# 185#------------------------------------------------------------------------- 186 187class ImportInfo: 188 """ 189 Class object that can hold information about the import 190 """ 191 keyorder = [PERSON_KEY, FAMILY_KEY, SOURCE_KEY, EVENT_KEY, MEDIA_KEY, 192 PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, TAG_KEY, CITATION_KEY] 193 key2data = { 194 PERSON_KEY : 0, 195 FAMILY_KEY : 1, 196 SOURCE_KEY: 2, 197 EVENT_KEY: 3, 198 MEDIA_KEY: 4, 199 PLACE_KEY: 5, 200 REPOSITORY_KEY: 6, 201 NOTE_KEY: 7, 202 TAG_KEY: 8, 203 CITATION_KEY: 9 204 } 205 206 def __init__(self): 207 """ 208 Init of the import class. 209 210 This creates the datastructures to hold info 211 """ 212 self.data_mergecandidate = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}] 213 self.data_newobject = [0] * 10 214 self.data_unknownobject = [0] * 10 215 self.data_families = '' 216 self.expl_note = '' 217 self.data_relpath = False 218 219 def add(self, category, key, obj, sec_obj=None): 220 """ 221 Add info of a certain category. Key is one of the predefined keys, 222 while obj is an object of which information will be extracted 223 """ 224 if category == 'merge-candidate': 225 self.data_mergecandidate[self.key2data[key]][obj.handle] = \ 226 self._extract_mergeinfo(key, obj, sec_obj) 227 elif category == 'new-object': 228 self.data_newobject[self.key2data[key]] += 1 229 elif category == 'unknown-object': 230 self.data_unknownobject[self.key2data[key]] += 1 231 elif category == 'relative-path': 232 self.data_relpath = True 233 elif category == 'unlinked-family': 234 # This is a bit ugly because it isn't using key in the same way as 235 # the rest of the categories, but it is only the calling routine 236 # that really knows what the error message should be. 237 self.data_families += key + "\n" 238 239 def _extract_mergeinfo(self, key, obj, sec_obj): 240 """ 241 Extract info from obj about 'merge-candidate', Key is one of the 242 predefined keys. 243 """ 244 if key == PERSON_KEY: 245 return _(" %(id)s - %(text)s with %(id2)s\n") % { 246 'id': obj.gramps_id, 247 'text' : name_displayer.display(obj), 248 'id2': sec_obj.gramps_id 249 } 250 elif key == FAMILY_KEY : 251 return _(" Family %(id)s with %(id2)s\n") % { 252 'id': obj.gramps_id, 'id2': sec_obj.gramps_id} 253 elif key == SOURCE_KEY: 254 return _(" Source %(id)s with %(id2)s\n") % { 255 'id': obj.gramps_id, 'id2': sec_obj.gramps_id} 256 elif key == EVENT_KEY: 257 return _(" Event %(id)s with %(id2)s\n") % { 258 'id': obj.gramps_id, 'id2': sec_obj.gramps_id} 259 elif key == MEDIA_KEY: 260 return _(" Media Object %(id)s with %(id2)s\n") % { 261 'id': obj.gramps_id, 'id2': sec_obj.gramps_id} 262 elif key == PLACE_KEY: 263 return _(" Place %(id)s with %(id2)s\n") % { 264 'id': obj.gramps_id, 'id2': sec_obj.gramps_id} 265 elif key == REPOSITORY_KEY: 266 return _(" Repository %(id)s with %(id2)s\n") % { 267 'id': obj.gramps_id, 'id2': sec_obj.gramps_id} 268 elif key == NOTE_KEY: 269 return _(" Note %(id)s with %(id2)s\n") % { 270 'id': obj.gramps_id, 'id2': sec_obj.gramps_id} 271 elif key == TAG_KEY: 272 pass # Tags can't be merged 273 elif key == CITATION_KEY: 274 return _(" Citation %(id)s with %(id2)s\n") % { 275 'id': obj.gramps_id, 'id2': sec_obj.gramps_id} 276 277 def info_text(self): 278 """ 279 Construct an info message from the data in the class. 280 """ 281 key2string = { 282 PERSON_KEY : _(' People: %d\n'), 283 FAMILY_KEY : _(' Families: %d\n'), 284 SOURCE_KEY : _(' Sources: %d\n'), 285 EVENT_KEY : _(' Events: %d\n'), 286 MEDIA_KEY : _(' Media Objects: %d\n'), 287 PLACE_KEY : _(' Places: %d\n'), 288 REPOSITORY_KEY : _(' Repositories: %d\n'), 289 NOTE_KEY : _(' Notes: %d\n'), 290 TAG_KEY : _(' Tags: %d\n'), 291 CITATION_KEY : _(' Citations: %d\n'), 292 } 293 txt = _("Number of new objects imported:\n") 294 for key in self.keyorder: 295 if any(self.data_unknownobject): 296 strng = key2string[key][0:-1] + ' (%d)\n' 297 txt += strng % (self.data_newobject[self.key2data[key]], 298 self.data_unknownobject[self.key2data[key]]) 299 else: 300 txt += key2string[key] % self.data_newobject[self.key2data[key]] 301 if any(self.data_unknownobject): 302 txt += _("\n The imported file was not self-contained.\n" 303 "To correct for that, %(new)d objects were created and\n" 304 "their typifying attribute was set to 'Unknown'.\n" 305 "The breakdown per category is depicted by the\n" 306 "number in parentheses. Where possible these\n" 307 "'Unkown' objects are referenced by note %(unknown)s.\n" 308 ) % {'new': sum(self.data_unknownobject), 'unknown': self.expl_note} 309 if self.data_relpath: 310 txt += _("\nMedia objects with relative paths have been\n" 311 "imported. These paths are considered relative to\n" 312 "the media directory you can set in the preferences,\n" 313 "or, if not set, relative to the user's directory.\n" 314 ) 315 merge = False 316 for key in self.keyorder: 317 if self.data_mergecandidate[self.key2data[key]]: 318 merge = True 319 break 320 if merge: 321 txt += _("\n\nObjects that are candidates to be merged:\n") 322 for key in self.keyorder: 323 datakey = self.key2data[key] 324 for handle in list(self.data_mergecandidate[datakey].keys()): 325 txt += self.data_mergecandidate[datakey][handle] 326 327 if self.data_families: 328 txt += "\n\n" 329 txt += self.data_families 330 331 return txt 332 333class LineParser: 334 def __init__(self, filename): 335 336 self.count = 0 337 self.person_count = 0 338 339 if GZIP_OK: 340 use_gzip = 1 341 try: 342 with gzip.open(filename, "r") as f: 343 f.read(1) 344 except IOError as msg: 345 use_gzip = 0 346 except ValueError as msg: 347 use_gzip = 1 348 else: 349 use_gzip = 0 350 351 try: 352 if use_gzip: 353 import io 354 # Bug 6255. TextIOWrapper is required for python3 to 355 # present file contents as text, otherwise they 356 # are read as binary. However due to a missing 357 # method (read1) in early python3 versions this 358 # try block will fail. 359 # Gramps will still import XML files using python 360 # versions < 3.3.0 but the file progress meter 361 # will not work properly, going immediately to 362 # 100%. 363 # It should work correctly from version 3.3. 364 ofile = io.TextIOWrapper(gzip.open(filename, "rb"), 365 encoding='utf8', errors='replace') 366 else: 367 ofile = open(filename, "r", encoding='utf8', errors='replace') 368 369 for line in ofile: 370 self.count += 1 371 if PERSON_RE.match(line): 372 self.person_count += 1 373 except: 374 self.count = 0 375 self.person_count = 0 376 finally: 377 # Ensure the file handle is always closed 378 ofile.close() 379 380 def get_count(self): 381 return self.count 382 383 def get_person_count(self): 384 return self.person_count 385 386#------------------------------------------------------------------------- 387# 388# ImportOpenFileContextManager 389# 390#------------------------------------------------------------------------- 391class ImportOpenFileContextManager: 392 """ 393 Context manager to open a file or stdin for reading. 394 """ 395 def __init__(self, filename, user): 396 self.filename = filename 397 self.filehandle = None 398 self.user = user 399 400 def __enter__(self): 401 if self.filename == '-': 402 try: 403 self.filehandle = sys.stdin.buffer 404 except: 405 self.filehandle = sys.stdin 406 else: 407 self.filehandle = self.open_file(self.filename) 408 return self.filehandle 409 410 def __exit__(self, exc_type, exc_value, traceback): 411 if self.filename != '-': 412 if self.filehandle: 413 self.filehandle.close() 414 return False 415 416 def open_file(self, filename): 417 """ 418 Open the xml file. 419 Return a valid file handle if the file opened sucessfully. 420 Return None if the file was not able to be opened. 421 """ 422 if GZIP_OK: 423 use_gzip = True 424 try: 425 with gzip.open(filename, "r") as ofile: 426 ofile.read(1) 427 except IOError as msg: 428 use_gzip = False 429 except ValueError as msg: 430 use_gzip = True 431 else: 432 use_gzip = False 433 434 try: 435 if use_gzip: 436 xml_file = gzip.open(filename, "rb") 437 else: 438 xml_file = open(filename, "rb") 439 except IOError as msg: 440 self.user.notify_error(_("%s could not be opened") % filename, str(msg)) 441 xml_file = None 442 except: 443 self.user.notify_error(_("%s could not be opened") % filename) 444 xml_file = None 445 446 return xml_file 447 448#------------------------------------------------------------------------- 449# 450# Gramps database parsing class. Derived from SAX XML parser 451# 452#------------------------------------------------------------------------- 453class GrampsParser(UpdateCallback): 454 455 def __init__(self, database, user, change, default_tag_format=None): 456 UpdateCallback.__init__(self, user.callback) 457 self.user = user 458 self.__gramps_version = 'unknown' 459 self.__xml_version = (1, 0, 0) 460 self.stext_list = [] 461 self.scomments_list = [] 462 self.note_list = [] 463 self.tlist = [] 464 self.conf = 2 465 self.gid2id = {} 466 self.gid2fid = {} 467 self.gid2eid = {} 468 self.gid2pid = {} 469 self.gid2oid = {} 470 self.gid2sid = {} 471 self.gid2rid = {} 472 self.gid2nid = {} 473 self.childref_map = {} 474 self.change = change 475 self.dp = parser 476 self.info = ImportInfo() 477 self.all_abs = True 478 self.db = database 479 # Data with handles already present in the db will overwrite existing 480 # data, so all imported data gets a new handle. This behavior is not 481 # needed and even unwanted if data is imported in an empty family tree 482 # because NarWeb urls are based on handles. Also for debugging purposes 483 # it can be advantageous to preserve the orginal handle. 484 self.replace_import_handle = (self.db.get_number_of_people() > 0 and 485 not LOG.isEnabledFor(logging.DEBUG)) 486 487 # Similarly, if the data is imported into an empty family tree, we also 488 # import the Researcher; if the tree was not empty, the existing 489 # Researcher is retained 490 self.import_researcher = self.db.get_total() == 0 491 self.ord = None 492 self.objref = None 493 self.object = None 494 self.repo = None 495 self.reporef = None 496 self.pref = None 497 self.use_p = 0 498 self.in_note = 0 499 self.in_stext = 0 500 self.in_scomments = 0 501 self.note = None 502 self.note_text = None 503 self.note_tags = [] 504 self.in_witness = False 505 self.photo = None 506 self.person = None 507 self.family = None 508 self.address = None 509 self.citation = None 510 self.in_old_sourceref = False 511 self.source = None 512 self.attribute = None 513 self.srcattribute = None 514 self.placeobj = None 515 self.placeref = None 516 self.place_name = None 517 self.locations = 0 518 self.place_names = 0 519 self.place_map = {} 520 self.place_import = PlaceImport(self.db) 521 522 self.resname = "" 523 self.resaddr = "" 524 self.reslocality = "" 525 self.rescity = "" 526 self.resstate = "" 527 self.rescon = "" 528 self.respos = "" 529 self.resphone = "" 530 self.resemail = "" 531 532 self.mediapath = "" 533 534 self.pmap = {} 535 self.fmap = {} 536 self.smap = {} 537 self.lmap = {} 538 self.media_file_map = {} 539 540 # List of new name formats and a dict for remapping them 541 self.name_formats = [] 542 self.name_formats_map = {} 543 self.taken_name_format_numbers = [num[0] 544 for num in self.db.name_formats] 545 546 self.event = None 547 self.eventref = None 548 self.childref = None 549 self.personref = None 550 self.name = None 551 self.surname = None 552 self.surnamepat = None 553 self.home = None 554 self.owner = Researcher() 555 self.func_list = [None]*50 556 self.func_index = 0 557 self.func = None 558 self.witness_comment = "" 559 self.idswap = {} 560 self.fidswap = {} 561 self.eidswap = {} 562 self.cidswap = {} 563 self.sidswap = {} 564 self.pidswap = {} 565 self.oidswap = {} 566 self.ridswap = {} 567 self.nidswap = {} 568 self.eidswap = {} 569 self.import_handles = {} 570 571 if default_tag_format: 572 name = time.strftime(default_tag_format) 573 tag = self.db.get_tag_from_name(name) 574 if tag: 575 self.default_tag = tag 576 else: 577 self.default_tag = Tag() 578 self.default_tag.set_name(name) 579 else: 580 self.default_tag = None 581 582 self.func_map = { 583 #name part 584 "name": (self.start_name, self.stop_name), 585 "first": (None, self.stop_first), 586 "call": (None, self.stop_call), 587 "aka": (self.start_name, self.stop_aka), #deprecated < 1.3.0 588 "last": (self.start_last, self.stop_last), #deprecated in 1.4.0 589 "nick": (None, self.stop_nick), 590 "title": (None, self.stop_title), 591 "suffix": (None, self.stop_suffix), 592 "patronymic": (self.start_patronymic, self.stop_patronymic), #deprecated in 1.4.0 593 "familynick": (None, self.stop_familynick), #new in 1.4.0 594 "group": (None, self.stop_group), #new in 1.4.0, replaces attribute 595 #new in 1.4.0 596 "surname": (self.start_surname, self.stop_surname), 597 # 598 "namemaps": (None, None), 599 "name-formats": (None, None), 600 #other 601 "address": (self.start_address, self.stop_address), 602 "addresses": (None, None), 603 "alt_name": (None, self.stop_alt_name), 604 "childlist": (None, None), 605 "attribute": (self.start_attribute, self.stop_attribute), 606 "attr_type": (None, self.stop_attr_type), 607 "attr_value": (None, self.stop_attr_value), 608 "srcattribute": (self.start_srcattribute, self.stop_srcattribute), 609 "bookmark": (self.start_bmark, None), 610 "bookmarks": (None, None), 611 "format": (self.start_format, None), 612 "child": (self.start_child, None), 613 "childof": (self.start_childof, None), 614 "childref": (self.start_childref, self.stop_childref), 615 "personref": (self.start_personref, self.stop_personref), 616 "citation": (self.start_citation, self.stop_citation), 617 "citationref": (self.start_citationref, None), 618 "citations": (None, None), 619 "city": (None, self.stop_city), 620 "county": (None, self.stop_county), 621 "country": (None, self.stop_country), 622 "comment": (None, self.stop_comment), 623 "confidence": (None, self.stop_confidence), 624 "created": (self.start_created, None), 625 "ref": (None, self.stop_ref), 626 "database": (self.start_database, self.stop_database), 627 "phone": (None, self.stop_phone), 628 "date": (None, self.stop_date), 629 "cause": (None, self.stop_cause), 630 "code": (None, self.stop_code), 631 "description": (None, self.stop_description), 632 "event": (self.start_event, self.stop_event), 633 "type": (None, self.stop_type), 634 "witness": (self.start_witness, self.stop_witness), 635 "eventref": (self.start_eventref, self.stop_eventref), 636 "data_item": (self.start_data_item, None), #deprecated in 1.6.0 637 "families": (None, self.stop_families), 638 "family": (self.start_family, self.stop_family), 639 "rel": (self.start_rel, None), 640 "region": (self.start_region, None), 641 "father": (self.start_father, None), 642 "gender": (None, self.stop_gender), 643 "header": (None, self.stop_header), 644 "map": (self.start_namemap, None), 645 "mediapath": (None, self.stop_mediapath), 646 "mother": (self.start_mother, None), 647 "note": (self.start_note, self.stop_note), 648 "noteref": (self.start_noteref, None), 649 "p": (None, self.stop_ptag), 650 "parentin": (self.start_parentin, None), 651 "people": (self.start_people, self.stop_people), 652 "person": (self.start_person, self.stop_person), 653 "img": (self.start_photo, self.stop_photo), 654 "objref": (self.start_objref, self.stop_objref), 655 "object": (self.start_media, self.stop_media), 656 "file": (self.start_file, None), 657 "page": (None, self.stop_page), 658 "place": (self.start_place, self.stop_place), 659 "dateval": (self.start_dateval, None), 660 "daterange": (self.start_daterange, None), 661 "datespan": (self.start_datespan, None), 662 "datestr": (self.start_datestr, None), 663 "places": (None, self.stop_places), 664 "placeobj": (self.start_placeobj, self.stop_placeobj), 665 "placeref": (self.start_placeref, self.stop_placeref), 666 "ptitle": (None, self.stop_ptitle), 667 "pname": (self.start_place_name, self.stop_place_name), 668 "locality": (None, self.stop_locality), 669 "location": (self.start_location, None), 670 "lds_ord": (self.start_lds_ord, self.stop_lds_ord), 671 "temple": (self.start_temple, None), 672 "status": (self.start_status, None), 673 "sealed_to": (self.start_sealed_to, None), 674 "coord": (self.start_coord, None), 675 "pos": (self.start_pos, None), 676 "postal": (None, self.stop_postal), 677 "range": (self.start_range, None), 678 "researcher": (None, self.stop_research), 679 "resname": (None, self.stop_resname), 680 "resaddr": (None, self.stop_resaddr), 681 "reslocality": (None, self.stop_reslocality), 682 "rescity": (None, self.stop_rescity), 683 "resstate": (None, self.stop_resstate), 684 "rescountry": (None, self.stop_rescountry), 685 "respostal": (None, self.stop_respostal), 686 "resphone": (None, self.stop_resphone), 687 "resemail": (None, self.stop_resemail), 688 "sauthor": (None, self.stop_sauthor), 689 "sabbrev": (None, self.stop_sabbrev), 690 "scomments": (None, self.stop_scomments), 691 "source": (self.start_source, self.stop_source), 692 "sourceref": (self.start_sourceref, self.stop_sourceref), 693 "sources": (None, None), 694 "spage": (None, self.stop_spage), 695 "spubinfo": (None, self.stop_spubinfo), 696 "state": (None, self.stop_state), 697 "stext": (None, self.stop_stext), 698 "stitle": (None, self.stop_stitle), 699 "street": (None, self.stop_street), 700 "style": (self.start_style, None), 701 "tag": (self.start_tag, self.stop_tag), 702 "tagref": (self.start_tagref, None), 703 "tags": (None, None), 704 "text": (None, self.stop_text), 705 "url": (self.start_url, None), 706 "repository": (self.start_repo, self.stop_repo), 707 "reporef": (self.start_reporef, self.stop_reporef), 708 "rname": (None, self.stop_rname), 709 } 710 self.grampsuri = re.compile(r"^gramps://(?P<object_class>[A-Z][a-z]+)/" 711 r"handle/(?P<handle>\w+)$") 712 713 def inaugurate(self, handle, target, prim_obj): 714 """ 715 Assign a handle (identity) to a primary object (and create it if it 716 doesn't exist yet) and add it to the database. 717 718 This method can be called with an object instance or with a 719 class object. Be aware that in the first case the side effect of this 720 function is to fill the object instance with the data read from the db. 721 In the second case, an empty object with the correct handle will be 722 created. 723 724 :param handle: The handle of the primary object, typically as read 725 directly from the XML attributes. 726 :type handle: str 727 :param target: Indicates the primary object type this handle relates to. 728 :type target: str, identical to target attr of bookmarks. 729 :param prim_obj: template of the primary object that is to be created. 730 :type prim_obj: Either an empty instance of a primary object or the 731 class object of a primary object. 732 :returns: The handle of the primary object. 733 :rtype: str 734 """ 735 handle = str(handle.replace('_', '')) 736 orig_handle = handle 737 if (orig_handle in self.import_handles and 738 target in self.import_handles[orig_handle]): 739 handle = self.import_handles[handle][target][HANDLE] 740 if not isinstance(prim_obj, abc.Callable): 741 # This method is called by a start_<primary_object> method. 742 get_raw_obj_data = {"person": self.db.get_raw_person_data, 743 "family": self.db.get_raw_family_data, 744 "event": self.db.get_raw_event_data, 745 "place": self.db.get_raw_place_data, 746 "source": self.db.get_raw_source_data, 747 "citation": self.db.get_raw_citation_data, 748 "repository": self.db.get_raw_repository_data, 749 "media": self.db.get_raw_media_data, 750 "note": self.db.get_raw_note_data, 751 "tag": self.db.get_raw_tag_data}[target] 752 raw = get_raw_obj_data(handle) 753 prim_obj.unserialize(raw) 754 self.import_handles[orig_handle][target][INSTANTIATED] = True 755 return handle 756 elif handle in self.import_handles: 757 LOG.warning("The file you import contains duplicate handles " 758 "which is illegal and being fixed now.") 759 handle = create_id() 760 while handle in self.import_handles: 761 handle = create_id() 762 self.import_handles[orig_handle][target] = [handle, False] 763 else: 764 orig_handle = handle 765 if self.replace_import_handle: 766 handle = create_id() 767 while handle in self.import_handles: 768 handle = create_id() 769 else: 770 has_handle_func = {"person": self.db.has_person_handle, 771 "family": self.db.has_family_handle, 772 "event": self.db.has_event_handle, 773 "place": self.db.has_place_handle, 774 "source": self.db.has_source_handle, 775 "citation": self.db.get_raw_citation_data, 776 "repository": self.db.has_repository_handle, 777 "media": self.db.has_media_handle, 778 "note": self.db.has_note_handle, 779 "tag": self.db.has_tag_handle}[target] 780 while has_handle_func(handle): 781 handle = create_id() 782 self.import_handles[orig_handle] = {target: [handle, False]} 783 # method is called by a reference 784 if isinstance(prim_obj, abc.Callable): 785 prim_obj = prim_obj() 786 else: 787 self.import_handles[orig_handle][target][INSTANTIATED] = True 788 prim_obj.set_handle(handle) 789 if target == "tag": 790 self.db.add_tag(prim_obj, self.trans) 791 else: 792 add_func = {"person": self.db.add_person, 793 "family": self.db.add_family, 794 "event": self.db.add_event, 795 "place": self.db.add_place, 796 "source": self.db.add_source, 797 "citation": self.db.add_citation, 798 "repository": self.db.add_repository, 799 "media": self.db.add_media, 800 "note": self.db.add_note}[target] 801 add_func(prim_obj, self.trans, set_gid=False) 802 return handle 803 804 def inaugurate_id(self, id_, key, prim_obj): 805 """ 806 Equivalent of inaugurate but for old style XML. 807 """ 808 if id_ is None: 809 raise GrampsImportError(_("The Gramps Xml you are trying to " 810 "import is malformed."), _("Attributes that link the data " 811 "together are missing.")) 812 id2handle_map = [self.gid2id, self.gid2fid, self.gid2sid, 813 self.gid2eid, self.gid2oid, self.gid2pid, 814 self.gid2rid, 'reference', self.gid2nid][key] 815 has_handle_func = [self.db.has_person_handle, 816 self.db.has_family_handle, 817 self.db.has_source_handle, 818 self.db.has_event_handle, 819 self.db.has_media_handle, 820 self.db.has_place_handle, 821 self.db.has_repository_handle, 822 'reference', 823 self.db.has_note_handle][key] 824 add_func = [self.db.add_person, 825 self.db.add_family, 826 self.db.add_source, 827 self.db.add_event, 828 self.db.add_media, 829 self.db.add_place, 830 self.db.add_repository, 831 'reference', 832 self.db.add_note][key] 833 get_raw_obj_data = [self.db.get_raw_person_data, 834 self.db.get_raw_family_data, 835 self.db.get_raw_source_data, 836 self.db.get_raw_event_data, 837 self.db.get_raw_media_data, 838 self.db.get_raw_place_data, 839 self.db.get_raw_repository_data, 840 'reference', self.db.get_raw_note_data][key] 841 id2id_map = [self.idswap, self.fidswap, self.sidswap, self.eidswap, 842 self.oidswap, self.pidswap, self.ridswap, 'reference', 843 self.nidswap][key] 844 id2user_format = [self.db.id2user_format, self.db.fid2user_format, 845 self.db.sid2user_format, self.db.eid2user_format, 846 self.db.oid2user_format, self.db.pid2user_format, 847 self.db.rid2user_format, 'reference', 848 self.db.nid2user_format][key] 849 find_next_gramps_id = [self.db.find_next_person_gramps_id, 850 self.db.find_next_family_gramps_id, 851 self.db.find_next_source_gramps_id, 852 self.db.find_next_event_gramps_id, 853 self.db.find_next_media_gramps_id, 854 self.db.find_next_place_gramps_id, 855 self.db.find_next_repository_gramps_id, 856 'reference', 857 self.db.find_next_note_gramps_id][key] 858 has_gramps_id = [self.db.has_person_gramps_id, 859 self.db.has_family_gramps_id, 860 self.db.has_source_gramps_id, 861 self.db.has_event_gramps_id, 862 self.db.has_media_gramps_id, 863 self.db.has_place_gramps_id, 864 self.db.has_repository_gramps_id, 865 'reference', 866 self.db.has_note_gramps_id][key] 867 868 gramps_id = self.legalize_id(id_, key, id2id_map, id2user_format, 869 find_next_gramps_id, has_gramps_id) 870 handle = id2handle_map.get(gramps_id) 871 if handle: 872 raw = get_raw_obj_data(handle) 873 prim_obj.unserialize(raw) 874 else: 875 handle = create_id() 876 while has_handle_func(handle): 877 handle = create_id() 878 if isinstance(prim_obj, abc.Callable): 879 prim_obj = prim_obj() 880 prim_obj.set_handle(handle) 881 prim_obj.set_gramps_id(gramps_id) 882 add_func(prim_obj, self.trans) 883 id2handle_map[gramps_id] = handle 884 return handle 885 886 def legalize_id(self, id_, key, gramps_ids, id2user_format, 887 find_next_gramps_id, has_gramps_id): 888 """ 889 Given an import id, adjust it so that it fits with the existing data. 890 891 :param id_: The id as it is in the Xml import file, might be None. 892 :type id_: str 893 :param key: Indicates kind of primary object this id is for. 894 :type key: int 895 :param gramps_ids: Dictionary with id's that have already been imported. 896 :type import_ids: dict 897 :param id2user_format: Function to convert a raw id into the format as 898 specified in the prefixes. 899 :type id2user_format: func 900 :param find_next_gramps_id: function to get the next available id. 901 :type find_next_gramps_id: func 902 :returns: The id. 903 :rtype: str 904 """ 905 gramps_id = id2user_format(id_) 906 if gramps_id is None or not gramps_ids.get(id_): 907 if gramps_id is None or has_gramps_id(gramps_id): 908 gramps_ids[id_] = find_next_gramps_id() 909 else: 910 gramps_ids[id_] = gramps_id 911 return gramps_ids[id_] 912 913 def parse(self, ifile, linecount=1, personcount=0): 914 """ 915 Parse the xml file 916 :param ifile: must be a file handle that is already open, with position 917 at the start of the file 918 """ 919 if personcount < 1000: 920 no_magic = True 921 else: 922 no_magic = False 923 with DbTxn(_("Gramps XML import"), self.db, batch=True, 924 no_magic=no_magic) as self.trans: 925 self.set_total(linecount) 926 927 self.db.disable_signals() 928 929 if self.default_tag and self.default_tag.handle is None: 930 self.db.add_tag(self.default_tag, self.trans) 931 932 self.p = ParserCreate() 933 self.p.StartElementHandler = self.startElement 934 self.p.EndElementHandler = self.endElement 935 self.p.CharacterDataHandler = self.characters 936 self.p.ParseFile(ifile) 937 938 if len(self.name_formats) > 0: 939 # add new name formats to the existing table 940 self.db.name_formats += self.name_formats 941 # Register new formats 942 name_displayer.set_name_format(self.db.name_formats) 943 944 # If the database was originally empty we update the researcher from 945 # the XML (or initialised to no researcher) 946 if self.import_researcher: 947 self.db.set_researcher(self.owner) 948 if self.home is not None: 949 person = self.db.get_person_from_handle(self.home) 950 self.db.set_default_person_handle(person.handle) 951 952 # Set media path 953 # The paths are normalized before being compared. 954 if self.mediapath: 955 if not self.db.get_mediapath(): 956 self.db.set_mediapath(self.mediapath) 957 elif not media_path(self.db) == expand_media_path(self.mediapath, self.db): 958 self.user.notify_error(_("Could not change media path"), 959 _("The opened file has media path %s, which conflicts with" 960 " the media path of the Family Tree you import into. " 961 "The original media path has been retained. Copy the " 962 "files to a correct directory or change the media " 963 "path in the Preferences." 964 ) % self.mediapath ) 965 966 self.fix_not_instantiated() 967 self.fix_families() 968 for key in list(self.func_map.keys()): 969 del self.func_map[key] 970 del self.func_map 971 del self.func_list 972 del self.p 973 del self.update 974 self.db.enable_signals() 975 self.db.request_rebuild() 976 return self.info 977 978 def start_database(self, attrs): 979 """ 980 Get the xml version of the file. 981 """ 982 if 'xmlns' in attrs: 983 xmlns = attrs.get('xmlns').split('/') 984 if len(xmlns)>= 2 and not xmlns[2] == 'gramps-project.org': 985 self.__xml_version = (0, 0, 0) 986 else: 987 try: 988 self.__xml_version = version_str_to_tup(xmlns[4], 3) 989 except: 990 #leave version at 1.0.0 although it could be 0.0.0 ?? 991 pass 992 else: 993 #1.0 or before xml, no dtd schema yet on 994 # http://www.gramps-project.org/xml/ 995 self.__xml_version = (0, 0, 0) 996 997 def start_created(self, attrs): 998 """ 999 Get the Gramps version that produced the file. 1000 """ 1001 if 'sources' in attrs: 1002 self.num_srcs = int(attrs['sources']) 1003 else: 1004 self.num_srcs = 0 1005 if 'places' in attrs: 1006 self.num_places = int(attrs['places']) 1007 else: 1008 self.num_places = 0 1009 if 'version' in attrs: 1010 self.__gramps_version = attrs.get('version') 1011 1012 def stop_header(self, *dummy): 1013 """ 1014 Check the version of Gramps and XML. 1015 """ 1016 xmlversion_str = '.'.join(str(i) for i in self.__xml_version) 1017 if self.__gramps_version == 'unknown': 1018 msg = _("The .gramps file you are importing does not contain information about " 1019 "the version of Gramps with, which it was produced.\n\n" 1020 "The file will not be imported.") 1021 raise GrampsImportError(_('Import file misses Gramps version'), msg) 1022 if self.__xml_version > libgrampsxml.GRAMPS_XML_VERSION_TUPLE: 1023 msg = _("The .gramps file you are importing was made by " 1024 "version %(newer)s of " 1025 "Gramps, while you are running an older version %(older)s. " 1026 "The file will not be imported. Please upgrade to the " 1027 "latest version of Gramps and try again." ) % { 1028 'newer' : self.__gramps_version, 'older' : VERSION } 1029 raise GrampsImportError('', msg) 1030 if self.__xml_version < (1, 0, 0): 1031 msg = _("The .gramps file you are importing was made by version " 1032 "%(oldgramps)s of Gramps, while you are running a more " 1033 "recent version %(newgramps)s.\n\n" 1034 "The file will not be imported. Please use an older version" 1035 " of Gramps that supports version %(xmlversion)s of the " 1036 "xml.\nSee\n %(gramps_wiki_xml_url)s\n for more info." 1037 ) % {'oldgramps': self.__gramps_version, 1038 'newgramps': VERSION, 1039 'xmlversion': xmlversion_str, 1040 'gramps_wiki_xml_url': URL_WIKISTRING + "Gramps_XML" , 1041 } 1042 raise GrampsImportError(_('The file will not be imported'), msg) 1043 elif self.__xml_version < (1, 1, 0): 1044 msg = _("The .gramps file you are importing was made by version " 1045 "%(oldgramps)s of Gramps, while you are running a much " 1046 "more recent version %(newgramps)s.\n\n" 1047 "Ensure after import everything is imported correctly. In " 1048 "the event of problems, please submit a bug and use an " 1049 "older version of Gramps in the meantime to import this " 1050 "file, which is version %(xmlversion)s of the xml.\nSee\n " 1051 "%(gramps_wiki_xml_url)s\nfor more info." 1052 ) % {'oldgramps': self.__gramps_version, 1053 'newgramps': VERSION, 1054 'xmlversion': xmlversion_str, 1055 'gramps_wiki_xml_url': URL_WIKISTRING + "Gramps_XML" , 1056 } 1057 self.user.warn(_('Old xml file'), msg) 1058 1059 def start_lds_ord(self, attrs): 1060 self.ord = LdsOrd() 1061 self.ord.set_type_from_xml(attrs['type']) 1062 self.ord.private = bool(attrs.get("priv")) 1063 if self.person: 1064 self.person.lds_ord_list.append(self.ord) 1065 elif self.family: 1066 self.family.lds_ord_list.append(self.ord) 1067 1068 def start_temple(self, attrs): 1069 self.ord.set_temple(attrs['val']) 1070 1071 def start_data_item(self, attrs): 1072 """ 1073 Deprecated in 1.6.0, replaced by srcattribute 1074 """ 1075 sat = SrcAttributeType(attrs['key']) 1076 sa = SrcAttribute() 1077 sa.set_type(sat) 1078 sa.set_value(attrs['value']) 1079 if self.source: 1080 self.source.add_attribute(sa) 1081 else: 1082 self.citation.add_attribute(sa) 1083 1084 def start_status(self, attrs): 1085 try: 1086 # old xml with integer statuses 1087 self.ord.set_status(int(attrs['val'])) 1088 except ValueError: 1089 # string 1090 self.ord.set_status_from_xml(attrs['val']) 1091 1092 def start_sealed_to(self, attrs): 1093 """ 1094 Add a family reference to the LDS ordinance currently processed. 1095 """ 1096 if 'hlink' in attrs: 1097 handle = self.inaugurate(attrs['hlink'], "family", Family) 1098 else: # old style XML 1099 handle = self.inaugurate_id(attrs.get('ref'), FAMILY_KEY, 1100 Family) 1101 self.ord.set_family_handle(handle) 1102 1103 def start_place(self, attrs): 1104 """A reference to a place in an object: event or lds_ord 1105 """ 1106 if 'hlink' in attrs: 1107 handle = self.inaugurate(attrs['hlink'], "place", Place) 1108 else: # old style XML 1109 handle = self.inaugurate_id(attrs.get('ref'), PLACE_KEY, 1110 Place) 1111 if self.ord: 1112 self.ord.set_place_handle(handle) 1113 elif self.event: 1114 self.event.set_place_handle(handle) 1115 1116 def start_placeobj(self, attrs): 1117 """ 1118 Add a place object to db if it doesn't exist yet and assign 1119 id, privacy and changetime. 1120 """ 1121 self.placeobj = Place() 1122 if 'handle' in attrs: 1123 orig_handle = attrs['handle'].replace('_', '') 1124 is_merge_candidate = (self.replace_import_handle and 1125 self.db.has_place_handle(orig_handle)) 1126 self.inaugurate(orig_handle, "place", self.placeobj) 1127 gramps_id = self.legalize_id(attrs.get('id'), PLACE_KEY, 1128 self.pidswap, self.db.pid2user_format, 1129 self.db.find_next_place_gramps_id, 1130 self.db.has_place_gramps_id) 1131 self.placeobj.set_gramps_id(gramps_id) 1132 if is_merge_candidate: 1133 orig_place = self.db.get_place_from_handle(orig_handle) 1134 self.info.add('merge-candidate', PLACE_KEY, orig_place, 1135 self.placeobj) 1136 else: 1137 self.inaugurate_id(attrs.get('id'), PLACE_KEY, self.placeobj) 1138 self.placeobj.private = bool(attrs.get("priv")) 1139 self.placeobj.change = int(attrs.get('change', self.change)) 1140 if self.__xml_version == (1, 6, 0): 1141 place_name = PlaceName() 1142 place_name.set_value(attrs.get('name', '')) 1143 self.placeobj.name = place_name 1144 if 'type' in attrs: 1145 self.placeobj.place_type.set_from_xml_str(attrs.get('type')) 1146 self.info.add('new-object', PLACE_KEY, self.placeobj) 1147 self.place_names = 0 1148 1149 # Gramps LEGACY: title in the placeobj tag 1150 self.placeobj.title = attrs.get('title', '') 1151 self.locations = 0 1152 self.update(self.p.CurrentLineNumber) 1153 if self.default_tag: 1154 self.placeobj.add_tag(self.default_tag.handle) 1155 return self.placeobj 1156 1157 def start_location(self, attrs): 1158 """Bypass the function calls for this one, since it appears to 1159 take up quite a bit of time""" 1160 1161 loc = Location() 1162 loc.street = attrs.get('street', '') 1163 loc.locality = attrs.get('locality', '') 1164 loc.city = attrs.get('city', '') 1165 loc.parish = attrs.get('parish', '') 1166 loc.county = attrs.get('county', '') 1167 loc.state = attrs.get('state', '') 1168 loc.country = attrs.get('country', '') 1169 loc.postal = attrs.get('postal', '') 1170 loc.phone = attrs.get('phone', '') 1171 1172 if self.__xml_version < (1, 6, 0): 1173 if self.locations > 0: 1174 self.placeobj.add_alternate_locations(loc) 1175 else: 1176 location = (attrs.get('street', ''), 1177 attrs.get('locality', ''), 1178 attrs.get('parish', ''), 1179 attrs.get('city', ''), 1180 attrs.get('county', ''), 1181 attrs.get('state', ''), 1182 attrs.get('country', '')) 1183 self.place_import.store_location(location, self.placeobj.handle) 1184 1185 for level, name in enumerate(location): 1186 if name: 1187 break 1188 place_name = PlaceName() 1189 place_name.set_value(name) 1190 self.placeobj.set_name(place_name) 1191 type_num = 7 - level if name else PlaceType.UNKNOWN 1192 self.placeobj.set_type(PlaceType(type_num)) 1193 codes = [attrs.get('postal'), attrs.get('phone')] 1194 self.placeobj.set_code(' '.join(code for code in codes if code)) 1195 else: 1196 self.placeobj.add_alternate_locations(loc) 1197 1198 self.locations = self.locations + 1 1199 1200 def start_witness(self, attrs): 1201 """ 1202 Add a note about a witness to the currently processed event or add 1203 an event reference connecting that event with a person assigning the 1204 role of witness. 1205 """ 1206 # Parse witnesses created by older gramps 1207 self.in_witness = True 1208 self.witness_comment = "" 1209 if 'name' in attrs: 1210 note = Note() 1211 note.handle = create_id() 1212 note.set(_("Witness name: %s") % attrs['name']) 1213 note.type.set(NoteType.EVENT) 1214 note.private = self.event.private 1215 self.db.add_note(note, self.trans) 1216 #set correct change time 1217 self.db.commit_note(note, self.trans, self.change) 1218 self.info.add('new-object', NOTE_KEY, note) 1219 self.event.add_note(note.handle) 1220 return 1221 1222 person = Person() 1223 if 'hlink' in attrs: 1224 self.inaugurate(attrs['hlink'], "person", person) 1225 elif 'ref' in attrs: 1226 self.inaugurate_id(attrs['ref'], PERSON_KEY, person) 1227 else: 1228 person = None 1229 1230 # Add an EventRef from that person 1231 # to this event using ROLE_WITNESS role 1232 if person: 1233 event_ref = EventRef() 1234 event_ref.ref = self.event.handle 1235 event_ref.role.set(EventRoleType.WITNESS) 1236 person.event_ref_list.append(event_ref) 1237 self.db.commit_person(person, self.trans, self.change) 1238 1239 def start_coord(self, attrs): 1240 self.placeobj.lat = attrs.get('lat', '') 1241 self.placeobj.long = attrs.get('long', '') 1242 1243 def start_event(self, attrs): 1244 """ 1245 Add an event object to db if it doesn't exist yet and assign 1246 id, privacy and changetime. 1247 """ 1248 if self.person or self.family: 1249 # Gramps LEGACY: old events that were written inside 1250 # person or family objects. 1251 self.event = Event() 1252 self.event.handle = create_id() 1253 self.event.type = EventType() 1254 self.event.type.set_from_xml_str(attrs['type']) 1255 self.db.add_event(self.event, self.trans) 1256 #set correct change time 1257 self.db.commit_event(self.event, self.trans, self.change) 1258 self.info.add('new-object', EVENT_KEY, self.event) 1259 else: 1260 # This is new event, with ID and handle already existing 1261 self.update(self.p.CurrentLineNumber) 1262 self.event = Event() 1263 if 'handle' in attrs: 1264 orig_handle = attrs['handle'].replace('_', '') 1265 is_merge_candidate = (self.replace_import_handle and 1266 self.db.has_event_handle(orig_handle)) 1267 self.inaugurate(orig_handle, "event", self.event) 1268 gramps_id = self.legalize_id(attrs.get('id'), EVENT_KEY, 1269 self.eidswap, self.db.eid2user_format, 1270 self.db.find_next_event_gramps_id, 1271 self.db.has_event_gramps_id) 1272 self.event.set_gramps_id(gramps_id) 1273 if is_merge_candidate: 1274 orig_event = self.db.get_event_from_handle(orig_handle) 1275 self.info.add('merge-candidate', EVENT_KEY, orig_event, 1276 self.event) 1277 else: #old style XML 1278 self.inaugurate_id(attrs.get('id'), EVENT_KEY, self.event) 1279 self.event.private = bool(attrs.get("priv")) 1280 self.event.change = int(attrs.get('change', self.change)) 1281 self.info.add('new-object', EVENT_KEY, self.event) 1282 if self.default_tag: 1283 self.event.add_tag(self.default_tag.handle) 1284 return self.event 1285 1286 def start_eventref(self, attrs): 1287 """ 1288 Add an event reference to the object currently processed. 1289 """ 1290 self.eventref = EventRef() 1291 if 'hlink' in attrs: 1292 handle = self.inaugurate(attrs['hlink'], "event", Event) 1293 else: # there is no old style XML 1294 raise GrampsImportError(_("The Gramps Xml you are trying to " 1295 "import is malformed."), _("Any event reference must have a " 1296 "'hlink' attribute.")) 1297 self.eventref.ref = handle 1298 self.eventref.private = bool(attrs.get('priv')) 1299 if 'role' in attrs: 1300 self.eventref.role.set_from_xml_str(attrs['role']) 1301 1302 # We count here on events being already parsed prior to parsing 1303 # people or families. This code will fail if this is not true. 1304 event = self.db.get_event_from_handle(self.eventref.ref) 1305 if not event: 1306 return 1307 1308 if self.family: 1309 event.personal = False 1310 self.family.add_event_ref(self.eventref) 1311 elif self.person: 1312 event.personal = True 1313 if (event.type == EventType.BIRTH) \ 1314 and (self.eventref.role == EventRoleType.PRIMARY) \ 1315 and (self.person.get_birth_ref() is None): 1316 self.person.set_birth_ref(self.eventref) 1317 elif (event.type == EventType.DEATH) \ 1318 and (self.eventref.role == EventRoleType.PRIMARY) \ 1319 and (self.person.get_death_ref() is None): 1320 self.person.set_death_ref(self.eventref) 1321 else: 1322 self.person.add_event_ref(self.eventref) 1323 1324 def start_placeref(self, attrs): 1325 """ 1326 Add a place reference to the place currently being processed. 1327 """ 1328 self.placeref = PlaceRef() 1329 handle = self.inaugurate(attrs['hlink'], "place", Place) 1330 self.placeref.ref = handle 1331 self.placeobj.add_placeref(self.placeref) 1332 1333 def start_attribute(self, attrs): 1334 self.attribute = Attribute() 1335 self.attribute.private = bool(attrs.get("priv")) 1336 self.attribute.type = AttributeType() 1337 if 'type' in attrs: 1338 self.attribute.type.set_from_xml_str(attrs["type"]) 1339 self.attribute.value = attrs.get("value", '') 1340 if self.photo: 1341 self.photo.add_attribute(self.attribute) 1342 elif self.object: 1343 self.object.add_attribute(self.attribute) 1344 elif self.objref: 1345 self.objref.add_attribute(self.attribute) 1346 elif self.event: 1347 self.event.add_attribute(self.attribute) 1348 elif self.eventref: 1349 self.eventref.add_attribute(self.attribute) 1350 elif self.person: 1351 self.person.add_attribute(self.attribute) 1352 elif self.family: 1353 self.family.add_attribute(self.attribute) 1354 1355 def start_srcattribute(self, attrs): 1356 self.srcattribute = SrcAttribute() 1357 self.srcattribute.private = bool(attrs.get("priv")) 1358 self.srcattribute.type = SrcAttributeType() 1359 if 'type' in attrs: 1360 self.srcattribute.type.set_from_xml_str(attrs["type"]) 1361 self.srcattribute.value = attrs.get("value", '') 1362 if self.source: 1363 self.source.add_attribute(self.srcattribute) 1364 elif self.citation: 1365 self.citation.add_attribute(self.srcattribute) 1366 1367 def start_address(self, attrs): 1368 self.address = Address() 1369 self.address.private = bool(attrs.get("priv")) 1370 1371 def start_bmark(self, attrs): 1372 """ 1373 Add a bookmark to db. 1374 """ 1375 target = attrs.get('target') 1376 if not target: 1377 # Old XML. Can be either handle or id reference 1378 # and this is guaranteed to be a person bookmark 1379 if 'hlink' in attrs: 1380 handle = self.inaugurate(attrs['hlink'], "person", 1381 Person) 1382 else: 1383 handle = self.inaugurate_id(attrs.get('ref'), PERSON_KEY, 1384 Person) 1385 self.db.bookmarks.append(handle) 1386 return 1387 1388 # This is new XML, so we are guaranteed to have a handle ref 1389 handle = attrs['hlink'].replace('_', '') 1390 handle = self.import_handles[handle][target][HANDLE] 1391 # Due to pre 2.2.9 bug, bookmarks might be handle of other object 1392 # Make sure those are filtered out. 1393 # Bookmarks are at end, so all handle must exist before we do bookmrks 1394 if target == 'person': 1395 if (self.db.get_person_from_handle(handle) is not None 1396 and handle not in self.db.bookmarks.get() ): 1397 self.db.bookmarks.append(handle) 1398 elif target == 'family': 1399 if (self.db.get_family_from_handle(handle) is not None 1400 and handle not in self.db.family_bookmarks.get() ): 1401 self.db.family_bookmarks.append(handle) 1402 elif target == 'event': 1403 if (self.db.get_event_from_handle(handle) is not None 1404 and handle not in self.db.event_bookmarks.get() ): 1405 self.db.event_bookmarks.append(handle) 1406 elif target == 'source': 1407 if (self.db.get_source_from_handle(handle) is not None 1408 and handle not in self.db.source_bookmarks.get() ): 1409 self.db.source_bookmarks.append(handle) 1410 elif target == 'citation': 1411 if (self.db.get_citation_from_handle(handle) is not None 1412 and handle not in self.db.citation_bookmarks.get() ): 1413 self.db.citation_bookmarks.append(handle) 1414 elif target == 'place': 1415 if (self.db.get_place_from_handle(handle) is not None 1416 and handle not in self.db.place_bookmarks.get() ): 1417 self.db.place_bookmarks.append(handle) 1418 elif target == 'media': 1419 if (self.db.get_media_from_handle(handle) is not None 1420 and handle not in self.db.media_bookmarks.get() ): 1421 self.db.media_bookmarks.append(handle) 1422 elif target == 'repository': 1423 if (self.db.get_repository_from_handle(handle) 1424 is not None and handle not in self.db.repo_bookmarks.get()): 1425 self.db.repo_bookmarks.append(handle) 1426 elif target == 'note': 1427 if (self.db.get_note_from_handle(handle) is not None 1428 and handle not in self.db.note_bookmarks.get() ): 1429 self.db.note_bookmarks.append(handle) 1430 1431 def start_format(self, attrs): 1432 number = int(attrs['number']) 1433 name = attrs['name'] 1434 fmt_str = attrs['fmt_str'] 1435 active = bool(attrs.get('active', True)) 1436 1437 if number in self.taken_name_format_numbers: 1438 number = self.remap_name_format(number) 1439 1440 self.name_formats.append((number, name, fmt_str, active)) 1441 1442 def remap_name_format(self, old_number): 1443 if old_number in self.name_formats_map: # This should not happen 1444 return self.name_formats_map[old_number] 1445 # Find the lowest new number not taken yet: 1446 new_number = -1 1447 while new_number in self.taken_name_format_numbers: 1448 new_number -= 1 1449 # Add this to the taken list 1450 self.taken_name_format_numbers.append(new_number) 1451 # Set up the mapping entry 1452 self.name_formats_map[old_number] = new_number 1453 # Return new number 1454 return new_number 1455 1456 def start_person(self, attrs): 1457 """ 1458 Add a person to db if it doesn't exist yet and assign 1459 id, privacy and changetime. 1460 """ 1461 self.update(self.p.CurrentLineNumber) 1462 self.person = Person() 1463 if 'handle' in attrs: 1464 orig_handle = attrs['handle'].replace('_', '') 1465 is_merge_candidate = (self.replace_import_handle and 1466 self.db.has_person_handle(orig_handle)) 1467 self.inaugurate(orig_handle, "person", self.person) 1468 gramps_id = self.legalize_id(attrs.get('id'), PERSON_KEY, 1469 self.idswap, self.db.id2user_format, 1470 self.db.find_next_person_gramps_id, 1471 self.db.has_person_gramps_id) 1472 self.person.set_gramps_id(gramps_id) 1473 if is_merge_candidate: 1474 orig_person = self.db.get_person_from_handle(orig_handle) 1475 self.info.add('merge-candidate', PERSON_KEY, orig_person, 1476 self.person) 1477 else: # old style XML 1478 self.inaugurate_id(attrs.get('id'), PERSON_KEY, self.person) 1479 self.person.private = bool(attrs.get("priv")) 1480 self.person.change = int(attrs.get('change', self.change)) 1481 self.info.add('new-object', PERSON_KEY, self.person) 1482 self.convert_marker(attrs, self.person) 1483 if self.default_tag: 1484 self.person.add_tag(self.default_tag.handle) 1485 return self.person 1486 1487 def start_people(self, attrs): 1488 """ 1489 Store the home person of the database. 1490 """ 1491 if 'home' in attrs: 1492 handle = self.inaugurate(attrs['home'], "person", Person) 1493 self.home = handle 1494 1495 def start_father(self, attrs): 1496 """ 1497 Add a father reference to the family currently processed. 1498 """ 1499 if 'hlink' in attrs: 1500 handle = self.inaugurate(attrs['hlink'], "person", Person) 1501 else: # old style XML 1502 handle = self.inaugurate_id(attrs.get('ref'), PERSON_KEY, 1503 Person) 1504 self.family.set_father_handle(handle) 1505 1506 def start_mother(self, attrs): 1507 """ 1508 Add a mother reference to the family currently processed. 1509 """ 1510 if 'hlink' in attrs: 1511 handle = self.inaugurate(attrs['hlink'], "person", Person) 1512 else: # old style XML 1513 handle = self.inaugurate_id(attrs.get('ref'), PERSON_KEY, 1514 Person) 1515 self.family.set_mother_handle(handle) 1516 1517 def start_child(self, attrs): 1518 """ 1519 Add a child reference to the family currently processed. 1520 1521 Here we are handling the old XML, in which 1522 frel and mrel belonged to the "childof" tag 1523 """ 1524 if 'hlink' in attrs: 1525 handle = self.inaugurate(attrs['hlink'], "person", Person) 1526 else: # old style XML 1527 handle = self.inaugurate_id(attrs.get('ref'), PERSON_KEY, 1528 Person) 1529 1530 # If that were the case then childref_map has the childref ready 1531 if (self.family.handle, handle) in self.childref_map: 1532 self.family.add_child_ref( 1533 self.childref_map[(self.family.handle, handle)]) 1534 1535 def start_childref(self, attrs): 1536 """ 1537 Add a child reference to the family currently processed. 1538 1539 Here we are handling the new XML, in which frel and mrel 1540 belong to the "childref" tag under family. 1541 """ 1542 self.childref = ChildRef() 1543 handle = self.inaugurate(attrs['hlink'], "person", Person) 1544 self.childref.ref = handle 1545 self.childref.private = bool(attrs.get('priv')) 1546 1547 mrel = ChildRefType() 1548 if attrs.get('mrel'): 1549 mrel.set_from_xml_str(attrs['mrel']) 1550 frel = ChildRefType() 1551 if attrs.get('frel'): 1552 frel.set_from_xml_str(attrs['frel']) 1553 1554 if not mrel.is_default(): 1555 self.childref.set_mother_relation(mrel) 1556 if not frel.is_default(): 1557 self.childref.set_father_relation(frel) 1558 self.family.add_child_ref(self.childref) 1559 1560 def start_personref(self, attrs): 1561 """ 1562 Add a person reference to the person currently processed. 1563 """ 1564 self.personref = PersonRef() 1565 if 'hlink' in attrs: 1566 handle = self.inaugurate(attrs['hlink'], "person", Person) 1567 else: # there is no old style XML 1568 raise GrampsImportError(_("The Gramps Xml you are trying to " 1569 "import is malformed."), _("Any person reference must have a " 1570 "'hlink' attribute.")) 1571 self.personref.ref = handle 1572 self.personref.private = bool(attrs.get('priv')) 1573 self.personref.rel = attrs['rel'] 1574 self.person.add_person_ref(self.personref) 1575 1576 def start_url(self, attrs): 1577 if "href" not in attrs: 1578 return 1579 url = Url() 1580 url.path = attrs["href"] 1581 url.set_description(attrs.get("description", '')) 1582 url.private = bool(attrs.get('priv')) 1583 url.type.set_from_xml_str(attrs.get('type', '')) 1584 if self.person: 1585 self.person.add_url(url) 1586 elif self.placeobj: 1587 self.placeobj.add_url(url) 1588 elif self.repo: 1589 self.repo.add_url(url) 1590 1591 def start_family(self, attrs): 1592 """ 1593 Add a family object to db if it doesn't exist yet and assign 1594 id, privacy and changetime. 1595 """ 1596 self.update(self.p.CurrentLineNumber) 1597 self.family = Family() 1598 if 'handle' in attrs: 1599 orig_handle = attrs['handle'].replace('_', '') 1600 is_merge_candidate = (self.replace_import_handle and 1601 self.db.has_family_handle(orig_handle)) 1602 self.inaugurate(orig_handle, "family", self.family) 1603 gramps_id = self.legalize_id(attrs.get('id'), FAMILY_KEY, 1604 self.fidswap, self.db.fid2user_format, 1605 self.db.find_next_family_gramps_id, 1606 self.db.has_family_gramps_id) 1607 self.family.set_gramps_id(gramps_id) 1608 if is_merge_candidate: 1609 orig_family = self.db.get_family_from_handle(orig_handle) 1610 self.info.add('merge-candidate', FAMILY_KEY, orig_family, 1611 self.family) 1612 else: # old style XML 1613 self.inaugurate_id(attrs.get('id'), FAMILY_KEY, self.family) 1614 self.family.private = bool(attrs.get("priv")) 1615 self.family.change = int(attrs.get('change', self.change)) 1616 self.info.add('new-object', FAMILY_KEY, self.family) 1617 # Gramps LEGACY: the type now belongs to <rel> tag 1618 # Here we need to support old format of <family type="Married"> 1619 if 'type' in attrs: 1620 self.family.type.set_from_xml_str(attrs["type"]) 1621 self.convert_marker(attrs, self.family) 1622 if self.default_tag: 1623 self.family.add_tag(self.default_tag.handle) 1624 return self.family 1625 1626 def start_rel(self, attrs): 1627 if 'type' in attrs: 1628 self.family.type.set_from_xml_str(attrs["type"]) 1629 1630 def start_file(self, attrs): 1631 self.object.mime = attrs['mime'] 1632 if 'description' in attrs: 1633 self.object.desc = attrs['description'] 1634 else: 1635 self.object.desc = "" 1636 #keep value of path, no longer make absolute paths on import 1637 src = attrs["src"] 1638 if src: 1639 self.object.path = src 1640 if self.all_abs and not os.path.isabs(src): 1641 self.all_abs = False 1642 self.info.add('relative-path', None, None) 1643 if 'checksum' in attrs: 1644 self.object.checksum = attrs['checksum'] 1645 else: 1646 if os.path.isabs(src): 1647 full_path = src 1648 else: 1649 full_path = os.path.join(self.mediapath, src) 1650 self.object.checksum = create_checksum(full_path) 1651 1652 def start_childof(self, attrs): 1653 """ 1654 Add a family reference to the person currently processed in which that 1655 person is a child. 1656 """ 1657 if 'hlink' in attrs: 1658 handle = self.inaugurate(attrs['hlink'], "family", Family) 1659 else: # old style XML 1660 handle = self.inaugurate_id(attrs.get('ref'), FAMILY_KEY, 1661 Family) 1662 1663 # Here we are handling the old XML, in which 1664 # frel and mrel belonged to the "childof" tag 1665 mrel = ChildRefType() 1666 frel = ChildRefType() 1667 if 'mrel' in attrs: 1668 mrel.set_from_xml_str(attrs['mrel']) 1669 if 'frel' in attrs: 1670 frel.set_from_xml_str(attrs['frel']) 1671 1672 childref = ChildRef() 1673 childref.ref = self.person.handle 1674 if not mrel.is_default(): 1675 childref.set_mother_relation(mrel) 1676 if not frel.is_default(): 1677 childref.set_father_relation(frel) 1678 self.childref_map[(handle, self.person.handle)] = childref 1679 self.person.add_parent_family_handle(handle) 1680 1681 def start_parentin(self, attrs): 1682 """ 1683 Add a family reference to the person currently processed in which that 1684 person is a parent. 1685 """ 1686 if 'hlink' in attrs: 1687 handle = self.inaugurate(attrs['hlink'], "family", Family) 1688 else: # old style XML 1689 handle = self.inaugurate_id(attrs.get('ref'), FAMILY_KEY, 1690 Family) 1691 self.person.add_family_handle(handle) 1692 1693 def start_name(self, attrs): 1694 if self.person: 1695 self.start_person_name(attrs) 1696 if self.placeobj: # XML 1.7.0 1697 self.start_place_name(attrs) 1698 1699 def start_place_name(self, attrs): 1700 self.place_name = PlaceName() 1701 self.place_name.set_value(attrs["value"]) 1702 if "lang" in attrs: 1703 self.place_name.set_language(attrs["lang"]) 1704 if self.place_names == 0: 1705 self.placeobj.set_name(self.place_name) 1706 else: 1707 self.placeobj.add_alternative_name(self.place_name) 1708 self.place_names += 1 1709 1710 def start_person_name(self, attrs): 1711 if not self.in_witness: 1712 self.name = Name() 1713 name_type = attrs.get('type', "Birth Name") 1714 # Mapping "Other Name" from gramps 2.0.x to Unknown 1715 if (self.__xml_version == (1, 0, 0)) and (name_type == 'Other Name'): 1716 self.name.set_type(NameType.UNKNOWN) 1717 else: 1718 self.name.type.set_from_xml_str(name_type) 1719 self.name.private = bool(attrs.get("priv", 0)) 1720 self.alt_name = bool(attrs.get("alt", 0)) 1721 try: 1722 sort_as = int(attrs["sort"]) 1723 # check if these pointers need to be remapped 1724 # and set the name attributes 1725 if sort_as in self.name_formats_map: 1726 self.name.sort_as = self.name_formats_map[sort_as] 1727 else: 1728 self.name.sort_as = sort_as 1729 except KeyError: 1730 pass 1731 try: 1732 display_as = int(attrs["display"]) 1733 # check if these pointers need to be remapped 1734 # and set the name attributes 1735 if display_as in self.name_formats_map: 1736 self.name.display_as = self.name_formats_map[display_as] 1737 else: 1738 self.name.display_as = display_as 1739 except KeyError: 1740 pass 1741 1742 def start_surname(self, attrs): 1743 self.surname = Surname() 1744 self.surname.set_prefix(attrs.get("prefix", "")) 1745 self.surname.set_primary(attrs.get("prim", "1") == "1") 1746 self.surname.set_connector(attrs.get("connector", "")) 1747 origin_type = attrs.get("derivation", "") 1748 self.surname.origintype.set_from_xml_str(origin_type) 1749 1750 def start_namemap(self, attrs): 1751 type = attrs.get('type') 1752 key = attrs['key'] 1753 value = attrs['value'] 1754 if type == 'group_as': 1755 if self.db.has_name_group_key(key): 1756 present = self.db.get_name_group_mapping(key) 1757 if not value == present: 1758 msg = _('Your Family Tree groups name "%(key)s" together' 1759 ' with "%(parent)s", did not change this grouping to "%(value)s".') % { 1760 'key' : key, 'parent' : present, 'value' : value } 1761 self.user.warn(_("Gramps ignored a name grouping"), msg) 1762 elif value != 'None': # None test fixes file corrupted by 11011 1763 self.db.set_name_group_mapping(key, value) 1764 1765 def start_last(self, attrs): 1766 """ This is the element in version < 1.4.0 to do the surname""" 1767 self.surname = Surname() 1768 self.surname.prefix = attrs.get('prefix', '') 1769 self.name.group_as = attrs.get('group', '') 1770 1771 def start_patronymic(self, attrs): 1772 """ This is the element in version < 1.4.0 to do the patronymic""" 1773 self.surnamepat = Surname() 1774 self.surnamepat.set_origintype(NameOriginType( 1775 NameOriginType.PATRONYMIC)) 1776 1777 def start_style(self, attrs): 1778 """ 1779 Styled text tag in notes (v1.4.0 onwards). 1780 """ 1781 tagtype = StyledTextTagType() 1782 tagtype.set_from_xml_str(attrs['name'].lower()) 1783 try: 1784 val = attrs['value'] 1785 match = self.grampsuri.match(val) 1786 if match: 1787 target = {"Person" : "person", "Family" : "family", 1788 "Event" : "event", "Place" : "place", 1789 "Source" : "source", "Citation" : "citation", 1790 "Repository" : "repository", "Media" : "media", 1791 "Note" : "note"}[str(match.group('object_class'))] 1792 if match.group('handle') in self.import_handles: 1793 if target in self.import_handles[match.group('handle')]: 1794 val = "gramps://%s/handle/%s" % ( 1795 match.group('object_class'), 1796 self.import_handles[match.group('handle')] 1797 [target][HANDLE]) 1798 tagvalue = StyledTextTagType.STYLE_TYPE[int(tagtype)](val) 1799 except KeyError: 1800 tagvalue = None 1801 except ValueError: 1802 return 1803 1804 self.note_tags.append(StyledTextTag(tagtype, tagvalue)) 1805 1806 def start_tag(self, attrs): 1807 """ 1808 Tag definition. 1809 """ 1810 if self.note is not None: 1811 # Styled text tag in notes (prior to v1.4.0) 1812 self.start_style(attrs) 1813 return 1814 1815 # Tag defintion 1816 self.tag = Tag() 1817 self.inaugurate(attrs['handle'], "tag", self.tag) 1818 self.tag.change = int(attrs.get('change', self.change)) 1819 self.info.add('new-object', TAG_KEY, self.tag) 1820 self.tag.set_name(attrs.get('name', _('Unknown when imported'))) 1821 self.tag.set_color(attrs.get('color', '#000000000000')) 1822 self.tag.set_priority(int(attrs.get('priority', 0))) 1823 return self.tag 1824 1825 def stop_tag(self, *tag): 1826 if self.note is not None: 1827 # Styled text tag in notes (prior to v1.4.0) 1828 return 1829 self.db.commit_tag(self.tag, self.trans, self.tag.get_change_time()) 1830 self.tag = None 1831 1832 def start_tagref(self, attrs): 1833 """ 1834 Tag reference in a primary object. 1835 """ 1836 handle = self.inaugurate(attrs['hlink'], "tag", Tag) 1837 1838 if self.person: 1839 self.person.add_tag(handle) 1840 1841 if self.family: 1842 self.family.add_tag(handle) 1843 1844 if self.object: 1845 self.object.add_tag(handle) 1846 1847 if self.note: 1848 self.note.add_tag(handle) 1849 1850 if self.event: 1851 self.event.add_tag(handle) 1852 1853 if self.placeobj: 1854 self.placeobj.add_tag(handle) 1855 1856 if self.repo: 1857 self.repo.add_tag(handle) 1858 1859 if self.source: 1860 self.source.add_tag(handle) 1861 1862 if self.citation: 1863 self.citation.add_tag(handle) 1864 1865 def start_range(self, attrs): 1866 self.note_tags[-1].ranges.append((int(attrs['start']), 1867 int(attrs['end']))) 1868 1869 def start_note(self, attrs): 1870 """ 1871 Add a note to db if it doesn't exist yet and assign 1872 id, privacy, changetime, format and type. 1873 """ 1874 self.in_note = 0 1875 if 'handle' in attrs: 1876 # This is new note, with ID and handle already existing 1877 self.update(self.p.CurrentLineNumber) 1878 self.note = Note() 1879 if 'handle' in attrs: 1880 orig_handle = attrs['handle'].replace('_', '') 1881 is_merge_candidate = (self.replace_import_handle and 1882 self.db.has_note_handle(orig_handle)) 1883 self.inaugurate(orig_handle, "note", self.note) 1884 gramps_id = self.legalize_id(attrs.get('id'), NOTE_KEY, 1885 self.nidswap, self.db.nid2user_format, 1886 self.db.find_next_note_gramps_id, 1887 self.db.has_note_gramps_id) 1888 self.note.set_gramps_id(gramps_id) 1889 if is_merge_candidate: 1890 orig_note = self.db.get_note_from_handle(orig_handle) 1891 self.info.add('merge-candicate', NOTE_KEY, orig_note, 1892 self.note) 1893 else: 1894 self.inaugurate_id(attrs.get('id'), NOTE_KEY, self.note) 1895 self.note.private = bool(attrs.get("priv")) 1896 self.note.change = int(attrs.get('change', self.change)) 1897 self.info.add('new-object', NOTE_KEY, self.note) 1898 self.note.format = int(attrs.get('format', Note.FLOWED)) 1899 self.note.type.set_from_xml_str(attrs.get('type', 1900 NoteType.UNKNOWN)) 1901 self.convert_marker(attrs, self.note) 1902 1903 # Since StyledText was introduced (XML v1.3.0) the clear text 1904 # part of the note is moved between <text></text> tags. 1905 # To catch the different versions here we reset the note_text 1906 # variable. It will be checked in stop_note() then. 1907 self.note_text = None 1908 self.note_tags = [] 1909 else: 1910 # Gramps LEGACY: old notes that were written inside other objects 1911 # We need to create a top-level note, it's type depends on 1912 # the caller object, and inherits privacy from caller object 1913 # On stop_note the reference to this note will be added 1914 self.note = Note() 1915 self.note.handle = create_id() 1916 self.note.format = int(attrs.get('format', Note.FLOWED)) 1917 # The order in this long if-then statement should reflect the 1918 # DTD: most deeply nested elements come first. 1919 if self.citation: 1920 self.note.type.set(NoteType.CITATION) 1921 self.note.private = self.citation.private 1922 elif self.address: 1923 self.note.type.set(NoteType.ADDRESS) 1924 self.note.private = self.address.private 1925 elif self.ord: 1926 self.note.type.set(NoteType.LDS) 1927 self.note.private = self.ord.private 1928 elif self.attribute: 1929 self.note.type.set(NoteType.ATTRIBUTE) 1930 self.note.private = self.attribute.private 1931 elif self.object: 1932 self.note.type.set(NoteType.MEDIA) 1933 self.note.private = self.object.private 1934 elif self.objref: 1935 self.note.type.set(NoteType.MEDIAREF) 1936 self.note.private = self.objref.private 1937 elif self.photo: 1938 self.note.type.set(NoteType.MEDIA) 1939 self.note.private = self.photo.private 1940 elif self.name: 1941 self.note.type.set(NoteType.PERSONNAME) 1942 self.note.private = self.name.private 1943 elif self.eventref: 1944 self.note.type.set(NoteType.EVENTREF) 1945 self.note.private = self.eventref.private 1946 elif self.reporef: 1947 self.note.type.set(NoteType.REPOREF) 1948 self.note.private = self.reporef.private 1949 elif self.source: 1950 self.note.type.set(NoteType.SOURCE) 1951 self.note.private = self.source.private 1952 elif self.event: 1953 self.note.type.set(NoteType.EVENT) 1954 self.note.private = self.event.private 1955 elif self.personref: 1956 self.note.type.set(NoteType.ASSOCIATION) 1957 self.note.private = self.personref.private 1958 elif self.person: 1959 self.note.type.set(NoteType.PERSON) 1960 self.note.private = self.person.private 1961 elif self.childref: 1962 self.note.type.set(NoteType.CHILDREF) 1963 self.note.private = self.childref.private 1964 elif self.family: 1965 self.note.type.set(NoteType.FAMILY) 1966 self.note.private = self.family.private 1967 elif self.placeobj: 1968 self.note.type.set(NoteType.PLACE) 1969 self.note.private = self.placeobj.private 1970 elif self.repo: 1971 self.note.type.set(NoteType.REPO) 1972 self.note.private = self.repo.private 1973 1974 self.db.add_note(self.note, self.trans) 1975 #set correct change time 1976 self.db.commit_note(self.note, self.trans, self.change) 1977 self.info.add('new-object', NOTE_KEY, self.note) 1978 if self.default_tag: 1979 self.note.add_tag(self.default_tag.handle) 1980 return self.note 1981 1982 def start_noteref(self, attrs): 1983 """ 1984 Add a note reference to the object currently processed. 1985 """ 1986 if 'hlink' in attrs: 1987 handle = self.inaugurate(attrs['hlink'], "note", Note) 1988 else: 1989 raise GrampsImportError(_("The Gramps Xml you are trying to " 1990 "import is malformed."), _("Any note reference must have a " 1991 "'hlink' attribute.")) 1992 1993 # The order in this long if-then statement should reflect the 1994 # DTD: most deeply nested elements come first. 1995 if self.citation: 1996 self.citation.add_note(handle) 1997 elif self.address: 1998 self.address.add_note(handle) 1999 elif self.ord: 2000 self.ord.add_note(handle) 2001 elif self.attribute: 2002 self.attribute.add_note(handle) 2003 elif self.object: 2004 self.object.add_note(handle) 2005 elif self.objref: 2006 self.objref.add_note(handle) 2007 elif self.photo: 2008 self.photo.add_note(handle) 2009 elif self.name: 2010 self.name.add_note(handle) 2011 elif self.eventref: 2012 self.eventref.add_note(handle) 2013 elif self.reporef: 2014 self.reporef.add_note(handle) 2015 elif self.source: 2016 self.source.add_note(handle) 2017 elif self.event: 2018 self.event.add_note(handle) 2019 elif self.personref: 2020 self.personref.add_note(handle) 2021 elif self.person: 2022 self.person.add_note(handle) 2023 elif self.childref: 2024 self.childref.add_note(handle) 2025 elif self.family: 2026 self.family.add_note(handle) 2027 elif self.placeobj: 2028 self.placeobj.add_note(handle) 2029 elif self.repo: 2030 self.repo.add_note(handle) 2031 2032 def __add_citation(self, citation_handle): 2033 """ 2034 Add a citation to the object currently processed. 2035 """ 2036 if self.photo: 2037 self.photo.add_citation(citation_handle) 2038 elif self.ord: 2039 self.ord.add_citation(citation_handle) 2040 elif self.attribute: 2041 self.attribute.add_citation(citation_handle) 2042 elif self.object: 2043 self.object.add_citation(citation_handle) 2044 elif self.objref: 2045 self.objref.add_citation(citation_handle) 2046 elif self.event: 2047 self.event.add_citation(citation_handle) 2048 elif self.address: 2049 self.address.add_citation(citation_handle) 2050 elif self.name: 2051 self.name.add_citation(citation_handle) 2052 elif self.placeobj: 2053 self.placeobj.add_citation(citation_handle) 2054 elif self.childref: 2055 self.childref.add_citation(citation_handle) 2056 elif self.family: 2057 self.family.add_citation(citation_handle) 2058 elif self.personref: 2059 self.personref.add_citation(citation_handle) 2060 elif self.person: 2061 self.person.add_citation(citation_handle) 2062 2063 def start_citationref(self, attrs): 2064 """ 2065 Add a citation reference to the object currently processed. 2066 """ 2067 handle = self.inaugurate(attrs['hlink'], "citation", Citation) 2068 2069 self.__add_citation(handle) 2070 2071 def start_citation(self, attrs): 2072 """ 2073 Add a citation object to db if it doesn't exist yet and assign 2074 id, privacy and changetime. 2075 """ 2076 self.update(self.p.CurrentLineNumber) 2077 self.citation = Citation() 2078 orig_handle = attrs['handle'].replace('_', '') 2079 is_merge_candidate = (self.replace_import_handle and 2080 self.db.has_citation_handle(orig_handle)) 2081 self.inaugurate(orig_handle, "citation", self.citation) 2082 gramps_id = self.legalize_id(attrs.get('id'), CITATION_KEY, 2083 self.cidswap, self.db.cid2user_format, 2084 self.db.find_next_citation_gramps_id, 2085 self.db.has_citation_gramps_id) 2086 self.citation.set_gramps_id(gramps_id) 2087 if is_merge_candidate: 2088 orig_citation = self.db.get_citation_from_handle(orig_handle) 2089 self.info.add('merge-candidate', CITATION_KEY, orig_citation, 2090 self.citation) 2091 self.citation.private = bool(attrs.get("priv")) 2092 self.citation.change = int(attrs.get('change', self.change)) 2093 self.citation.confidence = ( 2094 self.conf if self.__xml_version >= (1, 5, 1) 2095 else 0 ) # See bug# 7125 2096 self.info.add('new-object', CITATION_KEY, self.citation) 2097 if self.default_tag: 2098 self.citation.add_tag(self.default_tag.handle) 2099 return self.citation 2100 2101 def start_sourceref(self, attrs): 2102 """ 2103 Add a source reference to the object currently processed. 2104 """ 2105 if 'hlink' in attrs: 2106 handle = self.inaugurate(attrs['hlink'], "source", Source) 2107 else: 2108 handle = self.inaugurate_id(attrs.get('ref'), SOURCE_KEY, 2109 Source) 2110 2111 if self.citation: 2112 self.citation.set_reference_handle(handle) 2113 else: 2114 # Gramps LEGACY: Prior to v1.5.0 there were no citation objects. 2115 # We need to copy the contents of the old SourceRef into a new 2116 # Citation object. 2117 self.in_old_sourceref = True 2118 2119 self.citation = Citation() 2120 self.citation.set_reference_handle(handle) 2121 self.citation.confidence = int(attrs.get("conf", self.conf)) 2122 self.citation.private = bool(attrs.get("priv")) 2123 2124 citation_handle = self.db.add_citation(self.citation, self.trans) 2125 self.__add_citation(citation_handle) 2126 2127 def start_source(self, attrs): 2128 """ 2129 Add a source object to db if it doesn't exist yet and assign 2130 id, privacy and changetime. 2131 """ 2132 self.update(self.p.CurrentLineNumber) 2133 self.source = Source() 2134 if 'handle' in attrs: 2135 orig_handle = attrs['handle'].replace('_', '') 2136 is_merge_candidate = (self.replace_import_handle and 2137 self.db.has_source_handle(orig_handle)) 2138 self.inaugurate(orig_handle, "source", self.source) 2139 gramps_id = self.legalize_id(attrs.get('id'), SOURCE_KEY, 2140 self.sidswap, self.db.sid2user_format, 2141 self.db.find_next_source_gramps_id, 2142 self.db.has_source_gramps_id) 2143 self.source.set_gramps_id(gramps_id) 2144 if is_merge_candidate: 2145 orig_source = self.db.get_source_from_handle(orig_handle) 2146 self.info.add('merge-candidate', SOURCE_KEY, orig_source, 2147 self.source) 2148 else: # old style XML 2149 self.inaugurate_id(attrs.get('id'), SOURCE_KEY, self.source) 2150 self.source.private = bool(attrs.get("priv")) 2151 self.source.change = int(attrs.get('change', self.change)) 2152 self.info.add('new-object', SOURCE_KEY, self.source) 2153 if self.default_tag: 2154 self.source.add_tag(self.default_tag.handle) 2155 return self.source 2156 2157 def start_reporef(self, attrs): 2158 """ 2159 Add a repository reference to the source currently processed. 2160 """ 2161 self.reporef = RepoRef() 2162 if 'hlink' in attrs: 2163 handle = self.inaugurate(attrs['hlink'], "repository", 2164 Repository) 2165 else: # old style XML 2166 handle = self.inaugurate_id(attrs.get('ref'), REPOSITORY_KEY, 2167 Repository) 2168 self.reporef.ref = handle 2169 self.reporef.call_number = attrs.get('callno', '') 2170 if 'medium' in attrs: 2171 self.reporef.media_type.set_from_xml_str(attrs['medium']) 2172 self.reporef.private = bool(attrs.get("priv")) 2173 # we count here on self.source being available 2174 # reporefs can only be found within source 2175 self.source.add_repo_reference(self.reporef) 2176 2177 def start_objref(self, attrs): 2178 """ 2179 Add a media object reference to the object currently processed. 2180 """ 2181 self.objref = MediaRef() 2182 if 'hlink' in attrs: 2183 handle = self.inaugurate(attrs['hlink'], "media", 2184 Media) 2185 else: # old style XML 2186 handle = self.inaugurate_id(attrs.get('ref'), MEDIA_KEY, 2187 Media) 2188 self.objref.ref = handle 2189 self.objref.private = bool(attrs.get('priv')) 2190 if self.event: 2191 self.event.add_media_reference(self.objref) 2192 elif self.family: 2193 self.family.add_media_reference(self.objref) 2194 elif self.source: 2195 self.source.add_media_reference(self.objref) 2196 elif self.person: 2197 self.person.add_media_reference(self.objref) 2198 elif self.placeobj: 2199 self.placeobj.add_media_reference(self.objref) 2200 elif self.citation: 2201 self.citation.add_media_reference(self.objref) 2202 2203 def start_region(self, attrs): 2204 rect = (int(attrs.get('corner1_x')), 2205 int(attrs.get('corner1_y')), 2206 int(attrs.get('corner2_x')), 2207 int(attrs.get('corner2_y')) ) 2208 self.objref.set_rectangle(rect) 2209 2210 def start_media(self, attrs): 2211 """ 2212 Add a media object to db if it doesn't exist yet and assign 2213 id, privacy and changetime. 2214 """ 2215 self.object = Media() 2216 if 'handle' in attrs: 2217 orig_handle = attrs['handle'].replace('_', '') 2218 is_merge_candidate = (self.replace_import_handle and 2219 self.db.has_media_handle(orig_handle)) 2220 self.inaugurate(orig_handle, "media", self.object) 2221 gramps_id = self.legalize_id(attrs.get('id'), MEDIA_KEY, 2222 self.oidswap, self.db.oid2user_format, 2223 self.db.find_next_media_gramps_id, 2224 self.db.has_media_gramps_id) 2225 self.object.set_gramps_id(gramps_id) 2226 if is_merge_candidate: 2227 orig_media = self.db.get_media_from_handle(orig_handle) 2228 self.info.add('merge-candidate', MEDIA_KEY, orig_media, 2229 self.object) 2230 else: 2231 self.inaugurate_id(attrs.get('id'), MEDIA_KEY, self.object) 2232 self.object.private = bool(attrs.get("priv")) 2233 self.object.change = int(attrs.get('change', self.change)) 2234 self.info.add('new-object', MEDIA_KEY, self.object) 2235 2236 # Gramps LEGACY: src, mime, and description attributes 2237 # now belong to the <file> tag. Here we are supporting 2238 # the old format of <object src="blah"...> 2239 self.object.mime = attrs.get('mime', '') 2240 self.object.desc = attrs.get('description', '') 2241 src = attrs.get("src", '') 2242 if src: 2243 self.object.path = src 2244 if self.default_tag: 2245 self.object.add_tag(self.default_tag.handle) 2246 return self.object 2247 2248 def start_repo(self, attrs): 2249 """ 2250 Add a repository to db if it doesn't exist yet and assign 2251 id, privacy and changetime. 2252 """ 2253 self.repo = Repository() 2254 if 'handle' in attrs: 2255 orig_handle = attrs['handle'].replace('_', '') 2256 is_merge_candidate = (self.replace_import_handle and 2257 self.db.has_repository_handle(orig_handle)) 2258 self.inaugurate(orig_handle, "repository", self.repo) 2259 gramps_id = self.legalize_id(attrs.get('id'), REPOSITORY_KEY, 2260 self.ridswap, self.db.rid2user_format, 2261 self.db.find_next_repository_gramps_id, 2262 self.db.has_repository_gramps_id) 2263 self.repo.set_gramps_id(gramps_id) 2264 if is_merge_candidate: 2265 orig_repo = self.db.get_repository_from_handle(orig_handle) 2266 self.info.add('merge-candidate', REPOSITORY_KEY, orig_repo, 2267 self.repo) 2268 else: # old style XML 2269 self.inaugurate_id(attrs.get('id'), REPOSITORY_KEY, self.repo) 2270 self.repo.private = bool(attrs.get("priv")) 2271 self.repo.change = int(attrs.get('change', self.change)) 2272 self.info.add('new-object', REPOSITORY_KEY, self.repo) 2273 if self.default_tag: 2274 self.repo.add_tag(self.default_tag.handle) 2275 return self.repo 2276 2277 def stop_people(self, *tag): 2278 pass 2279 2280 def stop_database(self, *tag): 2281 self.update(self.p.CurrentLineNumber) 2282 2283 def stop_media(self, *tag): 2284 self.db.commit_media(self.object, self.trans, 2285 self.object.get_change_time()) 2286 self.object = None 2287 2288 def stop_objref(self, *tag): 2289 self.objref = None 2290 2291 def stop_repo(self, *tag): 2292 self.db.commit_repository(self.repo, self.trans, 2293 self.repo.get_change_time()) 2294 self.repo = None 2295 2296 def stop_reporef(self, *tag): 2297 self.reporef = None 2298 2299 def start_photo(self, attrs): 2300 self.photo = Media() 2301 self.pref = MediaRef() 2302 self.pref.set_reference_handle(self.photo.get_handle()) 2303 2304 for key in list(attrs.keys()): 2305 if key == "descrip" or key == "description": 2306 self.photo.set_description(attrs[key]) 2307 elif key == "priv": 2308 self.pref.set_privacy(int(attrs[key])) 2309 self.photo.set_privacy(int(attrs[key])) 2310 elif key == "src": 2311 src = attrs["src"] 2312 self.photo.set_path(src) 2313 else: 2314 attr = Attribute() 2315 attr.set_type(key) 2316 attr.set_value(attrs[key]) 2317 self.photo.add_attribute(attr) 2318 self.photo.set_mime_type(get_type(self.photo.get_path())) 2319 self.db.add_media(self.photo, self.trans) 2320 #set correct change time 2321 self.db.commit_media(self.photo, self.trans, self.change) 2322 self.info.add('new-object', MEDIA_KEY, self.photo) 2323 if self.family: 2324 self.family.add_media_reference(self.pref) 2325 elif self.source: 2326 self.source.add_media_reference(self.pref) 2327 elif self.person: 2328 self.person.add_media_reference(self.pref) 2329 elif self.placeobj: 2330 self.placeobj.add_media_reference(self.pref) 2331 2332 def start_daterange(self, attrs): 2333 self.start_compound_date(attrs, Date.MOD_RANGE) 2334 2335 def start_datespan(self, attrs): 2336 self.start_compound_date(attrs, Date.MOD_SPAN) 2337 2338 def start_compound_date(self, attrs, mode): 2339 if self.citation: 2340 date_value = self.citation.get_date_object() 2341 elif self.ord: 2342 date_value = self.ord.get_date_object() 2343 elif self.object: 2344 date_value = self.object.get_date_object() 2345 elif self.address: 2346 date_value = self.address.get_date_object() 2347 elif self.name: 2348 date_value = self.name.get_date_object() 2349 elif self.event: 2350 date_value = self.event.get_date_object() 2351 elif self.placeref: 2352 date_value = self.placeref.get_date_object() 2353 elif self.place_name: 2354 date_value = self.place_name.get_date_object() 2355 2356 start = attrs['start'].split('-') 2357 stop = attrs['stop'].split('-') 2358 2359 try: 2360 year = int(start[0]) 2361 except ValueError: 2362 year = 0 2363 2364 try: 2365 month = int(start[1]) 2366 except: 2367 month = 0 2368 2369 try: 2370 day = int(start[2]) 2371 except: 2372 day = 0 2373 2374 try: 2375 rng_year = int(stop[0]) 2376 except: 2377 rng_year = 0 2378 2379 try: 2380 rng_month = int(stop[1]) 2381 except: 2382 rng_month = 0 2383 2384 try: 2385 rng_day = int(stop[2]) 2386 except: 2387 rng_day = 0 2388 2389 if "cformat" in attrs: 2390 cal = Date.calendar_names.index(attrs['cformat']) 2391 else: 2392 cal = Date.CAL_GREGORIAN 2393 2394 if 'quality' in attrs: 2395 val = attrs['quality'] 2396 if val == 'estimated': 2397 qual = Date.QUAL_ESTIMATED 2398 elif val == 'calculated': 2399 qual = Date.QUAL_CALCULATED 2400 else: 2401 qual = Date.QUAL_NONE 2402 else: 2403 qual = Date.QUAL_NONE 2404 2405 dualdated = False 2406 if 'dualdated' in attrs: 2407 val = attrs['dualdated'] 2408 if val == "1": 2409 dualdated = True 2410 2411 newyear = Date.NEWYEAR_JAN1 2412 if 'newyear' in attrs: 2413 newyear = attrs['newyear'] 2414 if newyear.isdigit(): 2415 newyear = int(newyear) 2416 else: 2417 newyear = Date.newyear_to_code(newyear) 2418 2419 try: 2420 date_value.set(qual, mode, cal, 2421 (day, month, year, dualdated, 2422 rng_day, rng_month, rng_year, dualdated), 2423 newyear=newyear) 2424 except DateError as e: 2425 self._set_date_to_xml_text(date_value, e, 2426 xml_element_name = ("datespan" if mode == Date.MOD_SPAN 2427 else "daterange"), 2428 xml_attrs = attrs) 2429 2430 def start_dateval(self, attrs): 2431 if self.citation: 2432 date_value = self.citation.get_date_object() 2433 elif self.ord: 2434 date_value = self.ord.get_date_object() 2435 elif self.object: 2436 date_value = self.object.get_date_object() 2437 elif self.address: 2438 date_value = self.address.get_date_object() 2439 elif self.name: 2440 date_value = self.name.get_date_object() 2441 elif self.event: 2442 date_value = self.event.get_date_object() 2443 elif self.placeref: 2444 date_value = self.placeref.get_date_object() 2445 elif self.place_name: 2446 date_value = self.place_name.get_date_object() 2447 2448 bce = 1 2449 val = attrs['val'] 2450 if val[0] == '-': 2451 bce = -1 2452 val = val[1:] 2453 start = val.split('-') 2454 try: 2455 year = int(start[0])*bce 2456 except: 2457 year = 0 2458 2459 try: 2460 month = int(start[1]) 2461 except: 2462 month = 0 2463 2464 try: 2465 day = int(start[2]) 2466 except: 2467 day = 0 2468 2469 if "cformat" in attrs: 2470 cal = Date.calendar_names.index(attrs['cformat']) 2471 else: 2472 cal = Date.CAL_GREGORIAN 2473 2474 if 'type' in attrs: 2475 val = attrs['type'] 2476 if val == "about": 2477 mod = Date.MOD_ABOUT 2478 elif val == "after": 2479 mod = Date.MOD_AFTER 2480 else: 2481 mod = Date.MOD_BEFORE 2482 else: 2483 mod = Date.MOD_NONE 2484 2485 if 'quality' in attrs: 2486 val = attrs['quality'] 2487 if val == 'estimated': 2488 qual = Date.QUAL_ESTIMATED 2489 elif val == 'calculated': 2490 qual = Date.QUAL_CALCULATED 2491 else: 2492 qual = Date.QUAL_NONE 2493 else: 2494 qual = Date.QUAL_NONE 2495 2496 dualdated = False 2497 if 'dualdated' in attrs: 2498 val = attrs['dualdated'] 2499 if val == "1": 2500 dualdated = True 2501 2502 newyear = Date.NEWYEAR_JAN1 2503 if 'newyear' in attrs: 2504 newyear = attrs['newyear'] 2505 if newyear.isdigit(): 2506 newyear = int(newyear) 2507 else: 2508 newyear = Date.newyear_to_code(newyear) 2509 2510 try: 2511 date_value.set(qual, mod, cal, (day, month, year, dualdated), 2512 newyear=newyear) 2513 except DateError as e: 2514 self._set_date_to_xml_text(date_value, e, 'dateval', attrs) 2515 2516 def _set_date_to_xml_text(self, date_value, date_error, xml_element_name, xml_attrs): 2517 """ 2518 Common handling of invalid dates for the date... element handlers. 2519 2520 Prints warning on console and sets date_value to a text-only date 2521 with the problematic XML inside. 2522 """ 2523 xml = "<{element_name} {attrs}/>".format( 2524 element_name = xml_element_name, 2525 attrs = " ".join( 2526 ['{}="{}"'.format(k,escape(v, entities={'"' : """})) 2527 for k,v in xml_attrs.items()])) 2528 # TRANSLATORS: leave the {date} and {xml} untranslated in the format string, 2529 # but you may re-order them if needed. 2530 LOG.warning(_("Invalid date {date} in XML {xml}, preserving XML as text" 2531 ).format(date=date_error.date.__dict__, xml=xml)) 2532 date_value.set(modifier=Date.MOD_TEXTONLY, text=xml) 2533 2534 def start_datestr(self, attrs): 2535 if self.citation: 2536 date_value = self.citation.get_date_object() 2537 elif self.ord: 2538 date_value = self.ord.get_date_object() 2539 elif self.object: 2540 date_value = self.object.get_date_object() 2541 elif self.address: 2542 date_value = self.address.get_date_object() 2543 elif self.name: 2544 date_value = self.name.get_date_object() 2545 elif self.event: 2546 date_value = self.event.get_date_object() 2547 elif self.placeref: 2548 date_value = self.placeref.get_date_object() 2549 else: 2550 date_value = self.place_name.get_date_object() 2551 2552 date_value.set_as_text(attrs['val']) 2553 2554 def start_pos(self, attrs): 2555 self.person.position = (int(attrs["x"]), int(attrs["y"])) 2556 2557 def stop_attribute(self, *tag): 2558 self.attribute = None 2559 2560 def stop_srcattribute(self, *tag): 2561 self.srcattribute = None 2562 2563 def stop_comment(self, tag): 2564 # Parse witnesses created by older gramps 2565 if tag.strip(): 2566 self.witness_comment = tag 2567 else: 2568 self.witness_comment = "" 2569 2570 def stop_witness(self, tag): 2571 # Parse witnesses created by older gramps 2572 if self.witness_comment: 2573 text = self.witness_comment 2574 elif tag.strip(): 2575 text = tag 2576 else: 2577 text = None 2578 2579 if text is not None: 2580 note = Note() 2581 note.handle = create_id() 2582 note.set(_("Witness comment: %s") % text) 2583 note.type.set(NoteType.EVENT) 2584 note.private = self.event.private 2585 self.db.add_note(note, self.trans) 2586 #set correct change time 2587 self.db.commit_note(note, self.trans, self.change) 2588 self.info.add('new-object', NOTE_KEY, note) 2589 self.event.add_note(note.handle) 2590 self.in_witness = False 2591 2592 def stop_attr_type(self, tag): 2593 self.attribute.set_type(tag) 2594 2595 def stop_attr_value(self, tag): 2596 self.attribute.set_value(tag) 2597 2598 def stop_address(self, *tag): 2599 if self.person: 2600 self.person.add_address(self.address) 2601 elif self.repo: 2602 self.repo.add_address(self.address) 2603 self.address = None 2604 2605 def stop_places(self, *tag): 2606 self.placeobj = None 2607 2608 if self.__xml_version < (1, 6, 0): 2609 self.place_import.generate_hierarchy(self.trans) 2610 2611 def stop_photo(self, *tag): 2612 self.photo = None 2613 2614 def stop_ptitle(self, tag): 2615 self.placeobj.title = tag 2616 2617 def stop_code(self, tag): 2618 self.placeobj.code = tag 2619 2620 def stop_alt_name(self, tag): 2621 place_name = PlaceName() 2622 place_name.set_value(tag) 2623 self.placeobj.add_alternative_name(place_name) 2624 2625 def stop_placeobj(self, *tag): 2626 if self.placeobj.name.get_value() == '': 2627 self.placeobj.name.set_value(self.placeobj.title) 2628 self.db.commit_place(self.placeobj, self.trans, 2629 self.placeobj.get_change_time()) 2630 self.placeobj = None 2631 2632 def stop_family(self, *tag): 2633 self.db.commit_family(self.family, self.trans, 2634 self.family.get_change_time()) 2635 self.family = None 2636 2637 def stop_type(self, tag): 2638 if self.event: 2639 # Event type 2640 self.event.type.set_from_xml_str(tag) 2641 elif self.repo: 2642 # Repository type 2643 self.repo.type.set_from_xml_str(tag) 2644 2645 def stop_childref(self, tag): 2646 self.childref = None 2647 2648 def stop_personref(self, tag): 2649 self.personref = None 2650 2651 def stop_eventref(self, tag): 2652 self.eventref = None 2653 2654 def stop_placeref(self, tag): 2655 self.placeref = None 2656 2657 def stop_event(self, *tag): 2658 if self.family: 2659 ref = EventRef() 2660 ref.ref = self.event.handle 2661 ref.private = self.event.private 2662 ref.role.set(EventRoleType.FAMILY) 2663 self.family.add_event_ref(ref) 2664 elif self.person: 2665 ref = EventRef() 2666 ref.ref = self.event.handle 2667 ref.private = self.event.private 2668 ref.role.set(EventRoleType.PRIMARY) 2669 if (self.event.type == EventType.BIRTH) \ 2670 and (self.person.get_birth_ref() is None): 2671 self.person.set_birth_ref(ref) 2672 elif (self.event.type == EventType.DEATH) \ 2673 and (self.person.get_death_ref() is None): 2674 self.person.set_death_ref(ref) 2675 else: 2676 self.person.add_event_ref(ref) 2677 2678 if self.event.get_description() == "" and \ 2679 self.event.get_type() != EventType.CUSTOM: 2680 if self.family: 2681 text = EVENT_FAMILY_STR % { 2682 'event_name' : str(self.event.get_type()), 2683 'family' : family_name(self.family, self.db), 2684 } 2685 elif self.person: 2686 text = EVENT_PERSON_STR % { 2687 'event_name' : str(self.event.get_type()), 2688 'person' : name_displayer.display(self.person), 2689 } 2690 else: 2691 text = '' 2692 self.event.set_description(text) 2693 2694 self.db.commit_event(self.event, self.trans, 2695 self.event.get_change_time()) 2696 self.event = None 2697 2698 def stop_name(self, attrs): 2699 if self.person: 2700 self.stop_person_name(attrs) 2701 if self.placeobj: # XML 1.7.0 2702 self.stop_place_name(attrs) 2703 2704 def stop_place_name(self, tag): 2705 self.place_name = None 2706 2707 def stop_person_name(self, tag): 2708 if self.in_witness: 2709 # Parse witnesses created by older gramps 2710 note = Note() 2711 note.handle = create_id() 2712 note.set(_("Witness name: %s") % tag) 2713 note.type.set(NoteType.EVENT) 2714 note.private = self.event.private 2715 self.db.add_note(note, self.trans) 2716 #set correct change time 2717 self.db.commit_note(note, self.trans, self.change) 2718 self.info.add('new-object', NOTE_KEY, note) 2719 self.event.add_note(note.handle) 2720 else: 2721 #first correct old xml that has no nametype set 2722 if self.alt_name: 2723 # alternate name or former aka tag 2724 if self.name.get_type() == "": 2725 self.name.set_type(NameType.AKA) 2726 else: 2727 if self.name.get_type() == "": 2728 self.name.set_type(NameType.BIRTH) 2729 2730 #same logic as bsddb upgrade for xml < 1.4.0 which will 2731 #have a surnamepat and/or surname. From 1.4.0 surname has been 2732 #added to name in self.stop_surname 2733 if not self.surnamepat: 2734 #no patronymic, only add surname if present 2735 if self.surname: 2736 self.name.add_surname(self.surname) 2737 self.name.set_primary_surname(0) 2738 else: 2739 #a patronymic, if no surname, a single surname 2740 if not self.surname: 2741 self.name.add_surname(self.surnamepat) 2742 self.name.set_primary_surname(0) 2743 else: 2744 #two surnames, first patronymic, then surname which is primary 2745 self.name.add_surname(self.surnamepat) 2746 self.name.add_surname(self.surname) 2747 self.name.set_primary_surname(1) 2748 if self.alt_name: 2749 self.person.add_alternate_name(self.name) 2750 else: 2751 self.person.set_primary_name(self.name) 2752 self.name = None 2753 self.surname = None 2754 self.surnamepat = None 2755 2756 def stop_aka(self, tag): 2757 if self.name.get_type() == "": 2758 self.name.set_type(NameType.AKA) 2759 if not self.surnamepat: 2760 #no patronymic, only add surname if present 2761 if self.surname: 2762 self.name.add_surname(self.surname) 2763 self.name.set_primary_surname(0) 2764 else: 2765 #a patronymic, if no surname, a single surname 2766 if not self.surname: 2767 self.name.add_surname(self.surnamepat) 2768 self.name.set_primary_surname(0) 2769 else: 2770 #two surnames, first patronymic, then surname which is primary 2771 self.name.add_surname(self.surnamepat) 2772 self.name.add_surname(self.surname) 2773 self.name.set_primary_surname(1) 2774 self.person.add_alternate_name(self.name) 2775 self.name = None 2776 2777 def stop_rname(self, tag): 2778 # Repository name 2779 self.repo.name = tag 2780 2781 def stop_ref(self, tag): 2782 """ 2783 Parse witnesses created by older gramps 2784 """ 2785 person = Person() 2786 self.inaugurate_id(tag, PERSON_KEY, person) 2787 # Add an EventRef from that person 2788 # to this event using ROLE_WITNESS role 2789 event_ref = EventRef() 2790 event_ref.ref = self.event.handle 2791 event_ref.role.set(EventRoleType.WITNESS) 2792 person.event_ref_list.append(event_ref) 2793 self.db.commit_person(person, self.trans, self.change) 2794 2795 def stop_place(self, tag): 2796 """end of a reference to place, should do nothing ... 2797 Note, if we encounter <place>blabla</place> this method is called 2798 with tag='blabla 2799 """ 2800 ##place = None 2801 ##handle = None 2802 ##if self.place_ref is None: #todo, add place_ref in start and init 2803 ## #legacy cody? I see no reason for this, but it was present 2804 ## if tag in self.place_map: 2805 ## place = self.place_map[tag] 2806 ## handle = place.get_handle() 2807 ## place = None 2808 ## else: 2809 ## place = RelLib.Place() 2810 ## place.set_title(tag) 2811 ## handle = place.get_handle() 2812 ## if self.ord: 2813 ## self.ord.set_place_handle(handle) 2814 ## elif self.object: 2815 ## self.object.set_place_handle(handle) 2816 ## else: 2817 ## self.event.set_place_handle(handle) 2818 ## if place : 2819 ## self.db.commit_place(self.placeobj,self.trans,self.change) 2820 ##self.place_ref = None 2821 pass 2822 2823 def stop_date(self, tag): 2824 if tag: 2825 if self.address: 2826 set_date(self.address, tag) 2827 else: 2828 set_date(self.event, tag) 2829 2830 def stop_first(self, tag): 2831 # bug 9242 2832 if len(tag.splitlines()) != 1: 2833 tag = "".join(tag.splitlines()) 2834 self.name.set_first_name(tag) 2835 2836 def stop_call(self, tag): 2837 self.name.set_call_name(tag) 2838 2839 def stop_families(self, *tag): 2840 self.family = None 2841 2842 def stop_person(self, *tag): 2843 self.db.commit_person(self.person, self.trans, 2844 self.person.get_change_time()) 2845 self.person = None 2846 2847 def stop_description(self, tag): 2848 self.event.set_description(tag) 2849 2850 def stop_cause(self, tag): 2851 # The old event's cause is now an attribute 2852 attr = Attribute() 2853 attr.set_type(AttributeType.CAUSE) 2854 attr.set_value(tag) 2855 self.event.add_attribute(attr) 2856 2857 def stop_gender(self, tag): 2858 t = tag 2859 if t == "M": 2860 self.person.set_gender (Person.MALE) 2861 elif t == "F": 2862 self.person.set_gender (Person.FEMALE) 2863 else: 2864 self.person.set_gender (Person.UNKNOWN) 2865 2866 def stop_stitle(self, tag): 2867 self.source.title = tag 2868 2869 def stop_sourceref(self, *tag): 2870 # if we are in an old style sourceref we need to commit the citation 2871 if self.in_old_sourceref: 2872 self.db.commit_citation(self.citation, self.trans, 2873 self.citation.get_change_time()) 2874 self.citation = None 2875 self.in_old_sourceref = False 2876 2877 def stop_source(self, *tag): 2878 self.db.commit_source(self.source, self.trans, 2879 self.source.get_change_time()) 2880 self.source = None 2881 2882 def stop_citation(self, *tag): 2883 self.db.commit_citation(self.citation, self.trans, 2884 self.citation.get_change_time()) 2885 self.citation = None 2886 2887 def stop_sauthor(self, tag): 2888 self.source.author = tag 2889 2890 def stop_phone(self, tag): 2891 self.address.phone = tag 2892 2893 def stop_street(self, tag): 2894 self.address.street = tag 2895 2896 def stop_locality(self, tag): 2897 self.address.locality = tag 2898 2899 def stop_city(self, tag): 2900 self.address.city = tag 2901 2902 def stop_county(self, tag): 2903 self.address.county = tag 2904 2905 def stop_state(self, tag): 2906 self.address.state = tag 2907 2908 def stop_country(self, tag): 2909 self.address.country = tag 2910 2911 def stop_postal(self, tag): 2912 self.address.set_postal_code(tag) 2913 2914 def stop_spage(self, tag): 2915 # Valid for version <= 1.4.0 2916 self.citation.set_page(tag) 2917 2918 def stop_page(self, tag): 2919 # Valid for version >= 1.5.0 2920 self.citation.set_page(tag) 2921 2922 def stop_confidence(self, tag): 2923 # Valid for version >= 1.5.0 2924 self.citation.set_confidence_level(int(tag)) 2925 2926 def stop_lds_ord(self, *tag): 2927 self.ord = None 2928 2929 def stop_spubinfo(self, tag): 2930 self.source.set_publication_info(tag) 2931 2932 def stop_sabbrev(self, tag): 2933 self.source.set_abbreviation(tag) 2934 2935 def stop_stext(self, tag): 2936 if self.use_p: 2937 self.use_p = 0 2938 text = fix_spaces(self.stext_list) 2939 else: 2940 text = tag 2941 # This is old XML. We no longer have "text" attribute in soure_ref. 2942 # So we create a new note, commit, and add the handle to note list. 2943 note = Note() 2944 note.handle = create_id() 2945 note.private = self.citation.private 2946 note.set(text) 2947 note.type.set(NoteType.SOURCE_TEXT) 2948 self.db.add_note(note, self.trans) 2949 #set correct change time 2950 self.db.commit_note(note, self.trans, self.change) 2951 self.info.add('new-object', NOTE_KEY, note) 2952 self.citation.add_note(note.handle) 2953 2954 def stop_scomments(self, tag): 2955 if self.use_p: 2956 self.use_p = 0 2957 text = fix_spaces(self.scomments_list) 2958 else: 2959 text = tag 2960 note = Note() 2961 note.handle = create_id() 2962 note.private = self.citation.private 2963 note.set(text) 2964 note.type.set(NoteType.CITATION) 2965 self.db.add_note(note, self.trans) 2966 #set correct change time 2967 self.db.commit_note(note, self.trans, self.change) 2968 self.info.add('new-object', NOTE_KEY, note) 2969 self.citation.add_note(note.handle) 2970 2971 def stop_last(self, tag): 2972 if self.surname: 2973 self.surname.set_surname(tag) 2974 if not tag.strip() and not self.surname.get_prefix().strip(): 2975 #consider empty surname as no surname 2976 self.surname = None 2977 2978 def stop_surname(self, tag): 2979 """Add surname to name, validating only one primary.""" 2980 if self.name: 2981 self.surname.set_surname(tag) 2982 if any(sname.get_primary() for sname in self.name.get_surname_list()): 2983 self.surname.set_primary(False) 2984 self.name.add_surname(self.surname) 2985 self.surname = None 2986 2987 def stop_group(self, tag): 2988 """ group name of a name""" 2989 if self.name: 2990 self.name.set_group_as(tag) 2991 2992 def stop_suffix(self, tag): 2993 if self.name: 2994 self.name.set_suffix(tag) 2995 2996 def stop_patronymic(self, tag): 2997 if self.surnamepat: 2998 self.surnamepat.set_surname(tag) 2999 if not tag.strip(): 3000 self.surnamepat = None 3001 3002 def stop_title(self, tag): 3003 if self.name: 3004 self.name.set_title(tag) 3005 3006 def stop_nick(self, tag): 3007 """in < 1.3.0 nick is on person and mapped to attribute 3008 from 1.4.0 it is a name element 3009 """ 3010 if self.name: 3011 self.name.set_nick_name(tag) 3012 elif self.person: 3013 attr = Attribute() 3014 attr.set_type(AttributeType.NICKNAME) 3015 attr.set_value(tag) 3016 self.person.add_attribute(attr) 3017 3018 def stop_familynick(self, tag): 3019 if self.name: 3020 self.name.set_family_nick_name(tag) 3021 3022 def stop_text(self, tag): 3023 self.note_text = tag 3024 3025 def stop_note(self, tag): 3026 self.in_note = 0 3027 if self.use_p: 3028 self.use_p = 0 3029 text = fix_spaces(self.note_list) 3030 elif self.note_text is not None: 3031 text = self.note_text 3032 else: 3033 text = tag 3034 3035 self.note.set_styledtext(StyledText(text, self.note_tags)) 3036 3037 # The order in this long if-then statement should reflect the 3038 # DTD: most deeply nested elements come first. 3039 if self.address: 3040 self.address.add_note(self.note.handle) 3041 elif self.ord: 3042 self.ord.add_note(self.note.handle) 3043 elif self.attribute: 3044 self.attribute.add_note(self.note.handle) 3045 elif self.object: 3046 self.object.add_note(self.note.handle) 3047 elif self.objref: 3048 self.objref.add_note(self.note.handle) 3049 elif self.photo: 3050 self.photo.add_note(self.note.handle) 3051 elif self.name: 3052 self.name.add_note(self.note.handle) 3053 elif self.eventref: 3054 self.eventref.add_note(self.note.handle) 3055 elif self.reporef: 3056 self.reporef.add_note(self.note.handle) 3057 elif self.source: 3058 self.source.add_note(self.note.handle) 3059 elif self.event: 3060 self.event.add_note(self.note.handle) 3061 elif self.personref: 3062 self.personref.add_note(self.note.handle) 3063 elif self.person: 3064 self.person.add_note(self.note.handle) 3065 elif self.childref: 3066 self.childref.add_note(self.note.handle) 3067 elif self.family: 3068 self.family.add_note(self.note.handle) 3069 elif self.placeobj: 3070 self.placeobj.add_note(self.note.handle) 3071 elif self.repo: 3072 self.repo.add_note(self.note.handle) 3073 3074 self.db.commit_note(self.note, self.trans, self.note.get_change_time()) 3075 self.note = None 3076 3077 def stop_note_asothers(self, *tag): 3078 self.db.commit_note(self.note, self.trans, self.note.get_change_time()) 3079 self.note = None 3080 3081 def stop_research(self, tag): 3082 self.owner.set_name(self.resname) 3083 self.owner.set_address(self.resaddr) 3084 self.owner.set_locality(self.reslocality) 3085 self.owner.set_city(self.rescity) 3086 self.owner.set_state(self.resstate) 3087 self.owner.set_country(self.rescon) 3088 self.owner.set_postal_code(self.respos) 3089 self.owner.set_phone(self.resphone) 3090 self.owner.set_email(self.resemail) 3091 3092 def stop_resname(self, tag): 3093 self.resname = tag 3094 3095 def stop_resaddr(self, tag): 3096 self.resaddr = tag 3097 3098 def stop_reslocality(self, tag): 3099 self.reslocality = tag 3100 3101 def stop_rescity(self, tag): 3102 self.rescity = tag 3103 3104 def stop_resstate(self, tag): 3105 self.resstate = tag 3106 3107 def stop_rescountry(self, tag): 3108 self.rescon = tag 3109 3110 def stop_respostal(self, tag): 3111 self.respos = tag 3112 3113 def stop_resphone(self, tag): 3114 self.resphone = tag 3115 3116 def stop_resemail(self, tag): 3117 self.resemail = tag 3118 3119 def stop_mediapath(self, tag): 3120 self.mediapath = tag 3121 3122 def stop_ptag(self, tag): 3123 self.use_p = 1 3124 if self.in_note: 3125 self.note_list.append(tag) 3126 elif self.in_stext: 3127 self.stext_list.append(tag) 3128 elif self.in_scomments: 3129 self.scomments_list.append(tag) 3130 3131 def startElement(self, tag, attrs): 3132 self.func_list[self.func_index] = (self.func, self.tlist) 3133 self.func_index += 1 3134 self.tlist = [] 3135 3136 try: 3137 f, self.func = self.func_map[tag] 3138 if f: 3139 f(attrs) 3140 except KeyError: 3141 self.func_map[tag] = (None, None) 3142 self.func = None 3143 3144 def endElement(self, tag): 3145 if self.func: 3146 self.func(''.join(self.tlist)) 3147 self.func_index -= 1 3148 self.func, self.tlist = self.func_list[self.func_index] 3149 3150 def characters(self, data): 3151 if self.func: 3152 self.tlist.append(data) 3153 3154 def convert_marker(self, attrs, obj): 3155 """ 3156 Convert markers into tags. 3157 3158 Old and new markers: complete=1 and marker=word 3159 """ 3160 if attrs.get('complete'): # this is only true for complete=1 3161 tag_name = 'Complete' 3162 else: 3163 tag_name = attrs.get('marker') 3164 3165 if tag_name is not None: 3166 tag_name = _(tag_name) 3167 tag = self.db.get_tag_from_name(tag_name) 3168 if tag is None: 3169 tag = Tag() 3170 tag.set_name(tag_name) 3171 tag.set_priority(self.db.get_number_of_tags()) 3172 tag_handle = self.db.add_tag(tag, self.trans) 3173 else: 3174 tag_handle = tag.get_handle() 3175 obj.add_tag(tag_handle) 3176 3177 def fix_not_instantiated(self): 3178 uninstantiated = [] 3179 for orig_handle in self.import_handles.keys(): 3180 tglist = [target for target in self.import_handles[orig_handle].keys() if 3181 not self.import_handles[orig_handle][target][INSTANTIATED]] 3182 for target in tglist: 3183 uninstantiated += [(orig_handle, target)] 3184 if uninstantiated: 3185 expl_note = create_explanation_note(self.db) 3186 self.db.commit_note(expl_note, self.trans, time.time()) 3187 self.info.expl_note = expl_note.get_gramps_id() 3188 for orig_handle, target in uninstantiated: 3189 class_arg = {'handle': orig_handle, 'id': None, 'priv': False} 3190 if target == 'family': 3191 objs = make_unknown(class_arg, expl_note.handle, 3192 self.func_map[target][0], self.func_map[target][1], 3193 self.trans, db=self.db) 3194 elif target == 'citation': 3195 objs = make_unknown(class_arg, expl_note.handle, 3196 self.func_map[target][0], self.func_map[target][1], 3197 self.trans, 3198 source_class_func=self.func_map['source'][0], 3199 source_commit_func=self.func_map['source'][1], 3200 source_class_arg={'handle':create_id(), 'id':None, 'priv':False}) 3201 elif target == 'note': 3202 objs = make_unknown(class_arg, expl_note.handle, 3203 self.func_map[target][0], self.stop_note_asothers, 3204 self.trans) 3205 else: 3206 if target == 'place': 3207 target = 'placeobj' 3208 elif target == 'media': 3209 target = 'object' 3210 objs = make_unknown(class_arg, expl_note.handle, 3211 self.func_map[target][0], self.func_map[target][1], 3212 self.trans) 3213 for obj in objs: 3214 key = CLASS_TO_KEY_MAP[obj.__class__.__name__] 3215 self.info.add('unknown-object', key, obj) 3216 3217 def fix_families(self): 3218 # Fix any imported families where there is a link from the family to an 3219 # individual, but no corresponding link from the individual to the 3220 # family. 3221 for orig_handle in list(self.import_handles.keys()): 3222 for target in list(self.import_handles[orig_handle].keys()): 3223 if target == 'family': 3224 family_handle = self.import_handles[orig_handle][target][HANDLE] 3225 family = self.db.get_family_from_handle(family_handle) 3226 father_handle = family.get_father_handle() 3227 mother_handle = family.get_mother_handle() 3228 3229 if father_handle: 3230 father = self.db.get_person_from_handle(father_handle) 3231 if father and \ 3232 family_handle not in father.get_family_handle_list(): 3233 father.add_family_handle(family_handle) 3234 self.db.commit_person(father, self.trans) 3235 txt = _("Error: family '%(family)s'" 3236 " father '%(father)s'" 3237 " does not refer" 3238 " back to the family." 3239 " Reference added." % 3240 {'family' : family.gramps_id, 3241 'father' : father.gramps_id}) 3242 self.info.add('unlinked-family', txt, None) 3243 LOG.warning(txt) 3244 3245 if mother_handle: 3246 mother = self.db.get_person_from_handle(mother_handle) 3247 if mother and \ 3248 family_handle not in mother.get_family_handle_list(): 3249 mother.add_family_handle(family_handle) 3250 self.db.commit_person(mother, self.trans) 3251 txt = _("Error: family '%(family)s'" 3252 " mother '%(mother)s'" 3253 " does not refer" 3254 " back to the family." 3255 " Reference added." % 3256 {'family' : family.gramps_id, 3257 'mother' : mother.gramps_id}) 3258 self.info.add('unlinked-family', txt, None) 3259 LOG.warning(txt) 3260 3261 for child_ref in family.get_child_ref_list(): 3262 child_handle = child_ref.ref 3263 child = self.db.get_person_from_handle(child_handle) 3264 if child: 3265 if family_handle not in \ 3266 child.get_parent_family_handle_list(): 3267 # The referenced child has no reference to the 3268 # family. There was a link from the FAM record 3269 # to the child, but no FAMC link from the child 3270 # to the FAM. 3271 child.add_parent_family_handle(family_handle) 3272 self.db.commit_person(child, self.trans) 3273 txt = _("Error: family '%(family)s'" 3274 " child '%(child)s'" 3275 " does not " 3276 "refer back to the family. " 3277 "Reference added." % 3278 {'family' : family.gramps_id, 3279 'child' : child.gramps_id}) 3280 self.info.add('unlinked-family', txt, None) 3281 LOG.warning(txt) 3282 3283def append_value(orig, val): 3284 if orig: 3285 return "%s, %s" % (orig, val) 3286 else: 3287 return val 3288 3289def build_place_title(loc): 3290 "Builds a title from a location" 3291 value = "" 3292 if loc.parish: 3293 value = loc.parish 3294 if loc.city: 3295 value = append_value(value, loc.city) 3296 if loc.county: 3297 value = append_value(value, loc.county) 3298 if loc.state: 3299 value = append_value(value, loc.state) 3300 if loc.country: 3301 value = append_value(value, loc.country) 3302 return value 3303 3304