#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007  Donald N. Allingham
# Copyright (C) 2009       Douglas S. Blank
# Copyright (C) 2010       Nick Hall
# Copyright (C) 2011       Michiel D. Nauta
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

# $Id: ImportXml.py 17716 2011-06-08 15:23:15Z m_d_n $

#-------------------------------------------------------------------------
#
# Standard Python Modules
#
#-------------------------------------------------------------------------
import os
import sys
import time
from xml.parsers.expat import ExpatError, ParserCreate
from gen.ggettext import gettext as _
import re
import logging
LOG = logging.getLogger(".ImportXML")

#-------------------------------------------------------------------------
#
# Gramps Modules
#
#-------------------------------------------------------------------------
from QuestionDialog import ErrorDialog, WarningDialog
import gen.mime
import gen.lib
from gen.db import DbTxn
from Errors import GrampsImportError
import Utils
import DateHandler
from gen.display.name import displayer as name_displayer
from gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, EVENT_KEY, 
                            MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY,
                            TAG_KEY)
from gen.updatecallback import UpdateCallback
import const
import libgrampsxml

#-------------------------------------------------------------------------
#
# Try to detect the presence of gzip
#
#-------------------------------------------------------------------------
try:
    import gzip
    GZIP_OK = True
except:
    GZIP_OK = False

PERSON_RE = re.compile(r"\s*\<person\s(.*)$")

CHILD_REL_MAP = {
    "Birth"     : gen.lib.ChildRefType(gen.lib.ChildRefType.BIRTH), 
    "Adopted"   : gen.lib.ChildRefType(gen.lib.ChildRefType.ADOPTED), 
    "Stepchild" : gen.lib.ChildRefType(gen.lib.ChildRefType.STEPCHILD), 
    "Sponsored" : gen.lib.ChildRefType(gen.lib.ChildRefType.SPONSORED), 
    "Foster"    : gen.lib.ChildRefType(gen.lib.ChildRefType.FOSTER), 
    "Unknown"   : gen.lib.ChildRefType(gen.lib.ChildRefType.UNKNOWN), 
    }

EVENT_FAMILY_STR = _("%(event_name)s of %(family)s")
EVENT_PERSON_STR = _("%(event_name)s of %(person)s")

#-------------------------------------------------------------------------
#
# Importing data into the currently open database. 
# Must takes care of renaming media files according to their new IDs.
#
#-------------------------------------------------------------------------
def importData(database, filename, callback=None):
##    return Utils.profile(importDataPro, database, filename, callback, cl)
##def importDataPro(database, filename, callback=None, cl=0):

    filename = os.path.normpath(filename)
    basefile = os.path.dirname(filename)
    database.smap = {}
    database.pmap = {}
    database.fmap = {}
    line_cnt = 0
    person_cnt = 0
    
    with ImportOpenFileContextManager(filename) as xml_file:
        if xml_file is None:
            return
    
        if filename == '-':
            change = time.time()
        else:
            change = os.path.getmtime(filename)
        parser = GrampsParser(database, callback, change)

        if filename != '-':
            linecounter = LineParser(filename)
            line_cnt = linecounter.get_count()
            person_cnt = linecounter.get_person_count()
    
        read_only = database.readonly
        database.readonly = False
        
        try:
            info = parser.parse(xml_file, line_cnt, person_cnt)
        except GrampsImportError, err: # version error
            ErrorDialog(*err.messages())
            return
        except IOError, msg:
            ErrorDialog(_("Error reading %s") % filename, str(msg))
            import traceback
            traceback.print_exc()
            return
        except ExpatError, msg:
            ErrorDialog(_("Error reading %s") % filename, 
                        _("The file is probably either corrupt or not a "
                          "valid Gramps database."))
            return

    database.readonly = read_only
    return info

##  TODO - WITH MEDIA PATH, IS THIS STILL NEEDED? 
##         BETTER LEAVE ALL RELATIVE TO NEW RELATIVE PATH
##   save_path is in .gramps/dbbase, no good place !
##    # copy all local images into <database>.images directory
##    db_dir = os.path.abspath(os.path.dirname(database.get_save_path()))
##    db_base = os.path.basename(database.get_save_path())
##    img_dir = os.path.join(db_dir, db_base)
##    first = not os.path.exists(img_dir)
##    
##    for m_id in database.get_media_object_handles():
##        mobject = database.get_object_from_handle(m_id)
##        oldfile = mobject.get_path()
##        if oldfile and not os.path.isabs(oldfile):
##            if first:
##                os.mkdir(img_dir)
##                first = 0
##            newfile = os.path.join(img_dir, oldfile)
##
##            try:
##                oldfilename = os.path.join(basefile, oldfile)
##                shutil.copyfile(oldfilename, newfile)
##                try:
##                    shutil.copystat(oldfilename, newfile)
##                except:
##                    pass
##                mobject.set_path(newfile)
##                database.commit_media_object(mobject, None, change)
##            except (IOError, OSError), msg:
##                ErrorDialog(_('Could not copy file'), str(msg))

#-------------------------------------------------------------------------
#
# Remove extraneous spaces
#
#-------------------------------------------------------------------------

def rs(text):
    return ' '.join(text.split())

def fix_spaces(text_list):
    return '\n'.join(map(rs, text_list))

#-------------------------------------------------------------------------
#
# 
#
#-------------------------------------------------------------------------

class ImportInfo(object):
    """
    Class object that can hold information about the import
    """
    keyorder = [PERSON_KEY, FAMILY_KEY, SOURCE_KEY, EVENT_KEY, MEDIA_KEY, 
                PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, TAG_KEY]
    key2data = {
            PERSON_KEY : 0,
            FAMILY_KEY : 1,
            SOURCE_KEY: 2, 
            EVENT_KEY: 3, 
            MEDIA_KEY: 4, 
            PLACE_KEY: 5, 
            REPOSITORY_KEY: 6, 
            NOTE_KEY: 7,
            TAG_KEY: 8
            }
    
    def __init__(self):
        """
        Init of the import class.
        
        This creates the datastructures to hold info
        """
        self.data_mergecandidate = [{}, {}, {}, {}, {}, {}, {}, {}, {}]
        self.data_newobject = [0] * 9
        self.data_relpath = False
        
    def add(self, category, key, obj, sec_obj=None):
        """
        Add info of a certain category. Key is one of the predefined keys,
        while obj is an object of which information will be extracted
        """
        if category == 'merge-candidate':
            self.data_mergecandidate[self.key2data[key]][obj.handle] = \
                    self._extract_mergeinfo(key, obj, sec_obj)
        elif category == 'new-object':
            self.data_newobject[self.key2data[key]] += 1
        elif category == 'relative-path':
            self.data_relpath = True

    def _extract_mergeinfo(self, key, obj, sec_obj):
        """
        Extract info from obj about 'merge-candidate', Key is one of the 
        predefined keys.
        """
        if key == PERSON_KEY:
            return _("  %(id)s - %(text)s with %(id2)s\n") % {
                        'id': obj.gramps_id, 
                        'text' : name_displayer.display(obj),
                        'id2': sec_obj.gramps_id
                        }
        elif key == FAMILY_KEY :
            return _("  Family %(id)s with %(id2)s\n") % {
                        'id': obj.gramps_id, 'id2': sec_obj.gramps_id}
        elif key == SOURCE_KEY:
            return _("  Source %(id)s with %(id2)s\n") % {
                        'id': obj.gramps_id, 'id2': sec_obj.gramps_id}
        elif key == EVENT_KEY:
            return _("  Event %(id)s with %(id2)s\n") % {
                        'id': obj.gramps_id, 'id2': sec_obj.gramps_id}
        elif key == MEDIA_KEY:
            return _("  Media Object %(id)s with %(id2)s\n") % {
                        'id': obj.gramps_id, 'id2': sec_obj.gramps_id}
        elif key == PLACE_KEY:
            return _("  Place %(id)s with %(id2)s\n") % {
                        'id': obj.gramps_id, 'id2': sec_obj.gramps_id}
        elif key == REPOSITORY_KEY:
            return _("  Repository %(id)s with %(id2)s\n") % {
                        'id': obj.gramps_id, 'id2': sec_obj.gramps_id}
        elif key == NOTE_KEY:
            return _("  Note %(id)s with %(id2)s\n") % {
                        'id': obj.gramps_id, 'id2': sec_obj.gramps_id}
        elif key == TAG_KEY:
            pass # Tags can't be merged

    def info_text(self):
        """
        Construct an info message from the data in the class.
        """
        key2string = {
            PERSON_KEY      : _('  People: %d\n'),
            FAMILY_KEY      : _('  Families: %d\n'),
            SOURCE_KEY      : _('  Sources: %d\n'),
            EVENT_KEY       : _('  Events: %d\n'),
            MEDIA_KEY       : _('  Media Objects: %d\n'),
            PLACE_KEY       : _('  Places: %d\n'),
            REPOSITORY_KEY  : _('  Repositories: %d\n'),
            NOTE_KEY        : _('  Notes: %d\n'),
            TAG_KEY         : _('  Tags: %d\n'),
            }
        txt = _("Number of new objects imported:\n")
        for key in self.keyorder:
            txt += key2string[key] % self.data_newobject[self.key2data[key]]
        if self.data_relpath:
            txt += _("\nMedia objects with relative paths have been\n"
                     "imported. These paths are considered relative to\n"
                     "the media directory you can set in the preferences,\n"
                     "or, if not set, relative to the user's directory.\n"
                    )
        merge = False
        for key in self.keyorder:
            if self.data_mergecandidate[self.key2data[key]]:
                merge = True
                break
        if merge:
            txt += _("\n\nObjects that are candidates to be merged:\n")
            for key in self.keyorder:
                datakey = self.key2data[key]
                for handle in self.data_mergecandidate[datakey].keys():
                    txt += self.data_mergecandidate[datakey][handle]
        return txt

class LineParser(object):
    def __init__(self, filename):

        self.count = 0
        self.person_count = 0

        if GZIP_OK:
            use_gzip = 1
            try:
                f = gzip.open(filename, "r")
                f.read(1)
                f.close()
            except IOError, msg:
                use_gzip = 0
            except ValueError, msg:
                use_gzip = 1
        else:
            use_gzip = 0

        try:
            if use_gzip:
                ofile = gzip.open(filename, "rb")
            else:
                ofile = open(filename, "r")

            for line in ofile:
                self.count += 1
                if PERSON_RE.match(line):
                    self.person_count += 1

            ofile.close()
        except:
            self.count = 0
            self.person_count = 0

    def get_count(self):
        return self.count

    def get_person_count(self):
        return self.person_count

#-------------------------------------------------------------------------
#
# ImportOpenFileContextManager
#
#-------------------------------------------------------------------------
class ImportOpenFileContextManager:
    """
    Context manager to open a file or stdin for reading.
    """
    def __init__(self, filename):
        self.filename = filename
        self.filehandle = None

    def __enter__(self):
        if self.filename == '-':
            self.filehandle = sys.stdin
        else:
            self.filehandle = self.open_file(self.filename)
        return self.filehandle

    def __exit__(self, exc_type, exc_value, traceback):
        if self.filename != '-':
            self.filehandle.close()
        return False

    def open_file(self, filename):
        """ 
        Open the xml file.
        Return a valid file handle if the file opened sucessfully.
        Return None if the file was not able to be opened.
        """
        if GZIP_OK:
            use_gzip = True
            try:
                ofile = gzip.open(filename, "r")
                ofile.read(1)
                ofile.close()
            except IOError, msg:
                use_gzip = False
            except ValueError, msg:
                use_gzip = True
        else:
            use_gzip = False

        try:
            if use_gzip:
                xml_file = gzip.open(filename, "rb")
            else:
                xml_file = open(filename, "r")
        except IOError, msg:
            ErrorDialog(_("%s could not be opened") % filename, str(msg))
            xml_file = None
        except:
            ErrorDialog(_("%s could not be opened") % filename)
            xml_file = None
            
        return xml_file

#-------------------------------------------------------------------------
#
# Gramps database parsing class.  Derived from SAX XML parser
#
#-------------------------------------------------------------------------
class GrampsParser(UpdateCallback):

    def __init__(self, database, callback, change):
        UpdateCallback.__init__(self, callback)
        self.__gramps_version = 'unknown'
        self.__xml_version = '1.0.0'
        self.stext_list = []
        self.scomments_list = []
        self.note_list = []
        self.tlist = []
        self.conf = 2
        self.gid2id = {}
        self.gid2fid = {}
        self.gid2eid = {}
        self.gid2pid = {}
        self.gid2oid = {}
        self.gid2sid = {}
        self.gid2rid = {}
        self.gid2nid = {}
        self.childref_map = {}
        self.change = change
        self.dp = DateHandler.parser
        self.info = ImportInfo()
        self.all_abs = True
        self.db = database
        # Data with handles already present in the db will overwrite existing
        # data, so all imported data gets a new handle. This behavior is not
        # needed and even unwanted if data is imported in an empty family tree
        # because NarWeb urls are based on handles. Also for debugging purposes
        # it can be advantageous to preserve the orginal handle.
        self.replace_import_handle = (self.db.get_number_of_people() > 0 and
                                      not LOG.isEnabledFor(logging.DEBUG))
        
        self.ord = None
        self.objref = None
        self.object = None
        self.repo = None
        self.reporef = None
        self.pref = None
        self.use_p = 0
        self.in_note = 0
        self.in_stext = 0
        self.in_scomments = 0
        self.note = None
        self.note_text = None
        self.note_tags = []
        self.in_witness = False
        self.photo = None
        self.person = None
        self.family = None
        self.address = None
        self.source = None
        self.source_ref = None
        self.attribute = None
        self.placeobj = None
        self.locations = 0
        self.place_map = {}

        self.resname = ""
        self.resaddr = "" 
        self.reslocality = ""
        self.rescity = ""
        self.resstate = ""
        self.rescon = "" 
        self.respos = ""
        self.resphone = ""
        self.resemail = ""

        self.mediapath = ""

        self.pmap = {}
        self.fmap = {}
        self.smap = {}
        self.lmap = {}
        self.media_file_map = {}

        # List of new name formats and a dict for remapping them
        self.name_formats  = []
        self.name_formats_map = {}
        self.taken_name_format_numbers = [num[0] 
                                          for num in self.db.name_formats]
        
        self.event = None
        self.eventref = None
        self.childref = None
        self.personref = None
        self.name = None
        self.surname = None
        self.surnamepat = None
        self.home = None
        self.owner = gen.lib.Researcher()
        self.func_list = [None]*50
        self.func_index = 0
        self.func = None
        self.witness_comment = ""
        self.idswap = {}
        self.fidswap = {}
        self.eidswap = {}
        self.sidswap = {}
        self.pidswap = {}
        self.oidswap = {}
        self.ridswap = {}
        self.nidswap = {}
        self.eidswap = {}
        self.import_handles = {}

        self.func_map = {
            #name part
            "name": (self.start_name, self.stop_name),
            "first": (None, self.stop_first), 
            "call": (None, self.stop_call), 
            "aka": (self.start_name, self.stop_aka),     #deprecated < 1.3.0
            "last": (self.start_last, self.stop_last),   #deprecated in 1.4.0
            "nick": (None, self.stop_nick),
            "title": (None, self.stop_title),
            "suffix": (None, self.stop_suffix),
            "patronymic": (self.start_patronymic, self.stop_patronymic),  #deprecated in 1.4.0
            "familynick": (None, self.stop_familynick),  #new in 1.4.0
            "group": (None, self.stop_group),            #new in 1.4.0, replaces attribute
            #new in 1.4.0
            "surname": (self.start_surname, self.stop_surname),
            # 
            "namemaps": (None, None),
            "name-formats": (None, None),       
            #other
            "address": (self.start_address, self.stop_address), 
            "addresses": (None, None), 
            "childlist": (None, None),  
            "attribute": (self.start_attribute, self.stop_attribute), 
            "attr_type": (None, self.stop_attr_type), 
            "attr_value": (None, self.stop_attr_value), 
            "bookmark": (self.start_bmark, None), 
            "bookmarks": (None, None), 
            "format": (self.start_format, None), 
            "child": (self.start_child, None), 
            "childof": (self.start_childof, None), 
            "childref": (self.start_childref, self.stop_childref), 
            "personref": (self.start_personref, self.stop_personref), 
            "city": (None, self.stop_city), 
            "county": (None, self.stop_county), 
            "country": (None, self.stop_country), 
            "comment": (None, self.stop_comment), 
            "created": (self.start_created, None), 
            "ref": (None, self.stop_ref), 
            "database": (self.start_database, self.stop_database), 
            "phone": (None, self.stop_phone), 
            "date": (None, self.stop_date), 
            "cause": (None, self.stop_cause), 
            "description": (None, self.stop_description), 
            "event": (self.start_event, self.stop_event), 
            "type": (None, self.stop_type), 
            "witness": (self.start_witness, self.stop_witness), 
            "eventref": (self.start_eventref, self.stop_eventref), 
            "data_item": (self.start_data_item, None), 
            "families": (None, self.stop_families), 
            "family": (self.start_family, self.stop_family), 
            "rel": (self.start_rel, None), 
            "region": (self.start_region, None),
            "father": (self.start_father, None), 
            "gender": (None, self.stop_gender), 
            "header": (None, self.stop_header), 
            "map": (self.start_namemap, None),
            "mediapath": (None, self.stop_mediapath),
            "mother": (self.start_mother, None), 
            "note": (self.start_note, self.stop_note), 
            "noteref": (self.start_noteref, None), 
            "p": (None, self.stop_ptag), 
            "parentin": (self.start_parentin, None), 
            "people": (self.start_people, self.stop_people), 
            "person": (self.start_person, self.stop_person), 
            "img": (self.start_photo, self.stop_photo), 
            "objref": (self.start_objref, self.stop_objref), 
            "object": (self.start_object, self.stop_object), 
            "file": (self.start_file, None), 
            "place": (self.start_place, self.stop_place), 
            "dateval": (self.start_dateval, None), 
            "daterange": (self.start_daterange, None), 
            "datespan": (self.start_datespan, None), 
            "datestr": (self.start_datestr, None), 
            "places": (None, self.stop_places), 
            "placeobj": (self.start_placeobj, self.stop_placeobj), 
            "ptitle": (None, self.stop_ptitle), 
            "location": (self.start_location, None), 
            "lds_ord": (self.start_lds_ord, self.stop_lds_ord), 
            "temple": (self.start_temple, None), 
            "status": (self.start_status, None), 
            "sealed_to": (self.start_sealed_to, None), 
            "coord": (self.start_coord, None), 
            "pos": (self.start_pos, None), 
            "postal": (None, self.stop_postal),
            "range": (self.start_range, None),
            "researcher": (None, self.stop_research), 
            "resname": (None, self.stop_resname), 
            "resaddr": (None, self.stop_resaddr), 
            "reslocality": (None, self.stop_reslocality), 
            "rescity": (None, self.stop_rescity), 
            "resstate": (None, self.stop_resstate), 
            "rescountry": (None, self.stop_rescountry), 
            "respostal": (None, self.stop_respostal), 
            "resphone": (None, self.stop_resphone), 
            "resemail": (None, self.stop_resemail), 
            "sauthor": (None, self.stop_sauthor), 
            "sabbrev": (None, self.stop_sabbrev), 
            "scomments": (None, self.stop_scomments), 
            "source": (self.start_source, self.stop_source), 
            "sourceref": (self.start_sourceref, self.stop_sourceref), 
            "sources": (None, None), 
            "spage": (None, self.stop_spage), 
            "spubinfo": (None, self.stop_spubinfo), 
            "state": (None, self.stop_state), 
            "stext": (None, self.stop_stext), 
            "stitle": (None, self.stop_stitle), 
            "street": (None, self.stop_street), 
            "style": (self.start_style, None),
            "tag": (self.start_tag, None),
            "tagref": (self.start_tagref, None),
            "tags": (None, None),
            "text": (None, self.stop_text),
            "url": (self.start_url, None), 
            "repository": (self.start_repo, self.stop_repo), 
            "reporef": (self.start_reporef, self.stop_reporef), 
            "rname": (None, self.stop_rname), 
        }

    def inaugurate(self, handle, prim_obj, has_handle_func, add_func,
                   get_raw_obj_data):
        """
        Assign a handle (identity) to a primary object (and create it if it
        doesn't exist yet) and add it to the database.

        This method can be called with an object instance or with a
        class object. Be aware that in the first case the side effect of this
        function is to fill the object instance with the data read from the db.
        In the second case, an empty object with the correct handle will be
        created.

        :param handle: The handle of the primary object, typically as read
                       directly from the XML attributes.
        :type handle: str
        :param prim_obj: template of the primary object that is to be created.
        :type prim_obj: Either an empty instance of a primary object or the
                         class object of a primary object.
        :param has_handle_func: function to determine if the database contains
                                a given handle for a specific object type.
        :type has_handle_func: func
        :param add_func: function to add a primary object of a specific type
                         to the database.
        :type add_func: func
        :param get_raw_obj_data: function to read the content of a primary
                                 object from the db in serialized form.
        :type get_raw_obj_data: func
        :returns: The handle of the primary object.
        :rtype: str
        """
        handle = str(handle.replace('_', ''))
        if handle in self.import_handles:
            handle = self.import_handles[handle]
            if not callable(prim_obj): 
                # This method is called by a start_<primary_object> method.
                raw = get_raw_obj_data(handle)
                prim_obj.unserialize(raw)
        else:
            orig_handle = handle
            if self.replace_import_handle:
                handle = Utils.create_id()
                while handle in self.import_handles:
                    handle = Utils.create_id()
            else:
                while has_handle_func(handle):
                    handle = Utils.create_id()
            self.import_handles[orig_handle] = handle
            if callable(prim_obj): # method is called by a reference
                prim_obj = prim_obj()
            prim_obj.set_handle(handle)
            if add_func == self.db.add_tag:
                add_func(prim_obj, self.trans)
            else:
                add_func(prim_obj, self.trans, set_gid=False)
        return handle

    def inaugurate_id(self, id_, key, prim_obj):
        """
        Equivalent of inaugurate but for old style XML.
        """
        if id_ is None:
            raise GrampsImportError(_("The Gramps Xml you are trying to "
                "import is malformed."), _("Attributes that link the data "
                "together are missing."))
        id2handle_map = [self.gid2id, self.gid2fid, self.gid2sid,
                         self.gid2eid, self.gid2oid, self.gid2pid,
                         self.gid2rid, 'reference', self.gid2nid][key]
        has_handle_func = [self.db.has_person_handle,
                           self.db.has_family_handle,
                           self.db.has_source_handle,
                           self.db.has_event_handle,
                           self.db.has_object_handle,
                           self.db.has_place_handle,
                           self.db.has_repository_handle,
                           'reference',
                           self.db.has_note_handle][key]
        add_func = [self.db.add_person,
                    self.db.add_family,
                    self.db.add_source,
                    self.db.add_event,
                    self.db.add_object,
                    self.db.add_place,
                    self.db.add_repository,
                    'reference',
                    self.db.add_note][key]
        get_raw_obj_data = [self.db.get_raw_person_data,
                            self.db.get_raw_family_data,
                            self.db.get_raw_source_data,
                            self.db.get_raw_event_data,
                            self.db.get_raw_object_data,
                            self.db.get_raw_place_data,
                            self.db.get_raw_repository_data,
                            'reference', self.db.get_raw_note_data][key]
        id2id_map = [self.idswap, self.fidswap, self.sidswap, self.eidswap,
                     self.oidswap, self.pidswap, self.ridswap, 'reference',
                     self.nidswap][key]
        id2user_format = [self.db.id2user_format, self.db.fid2user_format,
                          self.db.sid2user_format, self.db.eid2user_format,
                          self.db.oid2user_format, self.db.pid2user_format,
                          self.db.rid2user_format, 'reference',
                          self.db.nid2user_format][key]
        find_next_gramps_id = [self.db.find_next_person_gramps_id,
                               self.db.find_next_family_gramps_id,
                               self.db.find_next_source_gramps_id,
                               self.db.find_next_event_gramps_id,
                               self.db.find_next_object_gramps_id,
                               self.db.find_next_place_gramps_id,
                               self.db.find_next_repository_gramps_id,
                               'reference',
                               self.db.find_next_note_gramps_id][key]

        gramps_id = self.legalize_id(id_, key, id2id_map, id2user_format,
                                     find_next_gramps_id)
        handle = id2handle_map.get(gramps_id)
        if handle:
            raw = get_raw_obj_data(handle)
            prim_obj.unserialize(raw)
        else:
            handle = Utils.create_id()
            while has_handle_func(handle):
                handle = Utils.create_id()
            if callable(prim_obj):
                prim_obj = prim_obj()
            prim_obj.set_handle(handle)
            prim_obj.set_gramps_id(gramps_id)
            add_func(prim_obj, self.trans)
            id2handle_map[gramps_id] = handle
        return handle

    def legalize_id(self, id_, key, gramps_ids, id2user_format,
                    find_next_gramps_id):
        """
        Given an import id, adjust it so that it fits with the existing data.
        
        :param id_: The id as it is in the Xml import file, might be None.
        :type id_: str
        :param key: Indicates kind of primary object this id is for.
        :type key: int
        :param gramps_ids: Dictionary with id's that have already been imported.
        :type import_ids: dict
        :param id2user_format: Function to convert a raw id into the format as
                               specified in the prefixes.
        :type id2user_format: func
        :param find_next_gramps_id: function to get the next available id.
        :type find_next_gramps_id: func
        :returns: The id.
        :rtype: str
        """
        gramps_id = id2user_format(id_)
        if gramps_id is None or not gramps_ids.get(gramps_id):
            if gramps_id is None or self.db.has_gramps_id(key, gramps_id):
                gramps_ids[gramps_id] = find_next_gramps_id()
            else:
                gramps_ids[gramps_id] = gramps_id
        return gramps_ids[gramps_id]

    def parse(self, ifile, linecount=0, personcount=0):
        """
        Parse the xml file
        :param ifile: must be a file handle that is already open, with position
                      at the start of the file
        """
        if personcount < 1000:
            no_magic = True
        else:
            no_magic = False
        with DbTxn(_("Gramps XML import"), self.db, batch=True,
                   no_magic=no_magic) as self.trans:
            self.set_total(linecount)

            self.db.disable_signals()

            self.p = ParserCreate()
            self.p.StartElementHandler = self.startElement
            self.p.EndElementHandler = self.endElement
            self.p.CharacterDataHandler = self.characters
            self.p.ParseFile(ifile)

            if len(self.name_formats) > 0:
                # add new name formats to the existing table
                self.db.name_formats += self.name_formats
                # Register new formats
                name_displayer.set_name_format(self.db.name_formats)
    
            self.db.set_researcher(self.owner)
            if self.home is not None:
                person = self.db.get_person_from_handle(self.home)
                self.db.set_default_person_handle(person.handle)
    
            #set media path, this should really do some parsing to convert eg
            # windows path to unix ?
            if self.mediapath:
                oldpath = self.db.get_mediapath()
                if not oldpath:
                    self.db.set_mediapath(self.mediapath)
                elif not oldpath == self.mediapath:
                    ErrorDialog(_("Could not change media path"), 
                        _("The opened file has media path %s, which conflicts with"
                          " the media path of the family tree you import into. "
                          "The original media path has been retained. Copy the "
                          "files to a correct directory or change the media "
                          "path in the Preferences."
                         ) % self.mediapath )
    
            for key in self.func_map.keys():
                del self.func_map[key]
            del self.func_map
            del self.func_list
            del self.p
        self.db.enable_signals()
        self.db.request_rebuild()
        return self.info

    def start_database(self, attrs):
        """
        Get the xml version of the file.
        """
        if 'xmlns' in attrs:
            xmlns = attrs.get('xmlns').split('/')
            if len(xmlns)>= 2 and not xmlns[2] == 'gramps-project.org':
                self.__xml_version = '0.0.0'
            else:
                try:
                    self.__xml_version = xmlns[4]
                except:
                    #leave version at 1.0.0 although it could be 0.0.0 ??
                    pass
        else:
            #1.0 or before xml, no dtd schema yet on 
            # http://www.gramps-project.org/xml/
            self.__xml_version = '0.0.0'

    def start_created(self, attrs):
        """
        Get the Gramps version that produced the file.
        """
        if 'sources' in attrs:
            self.num_srcs = int(attrs['sources'])
        else:
            self.num_srcs = 0
        if 'places' in attrs:
            self.num_places = int(attrs['places'])
        else:
            self.num_places = 0
        if 'version' in attrs:
            self.__gramps_version = attrs.get('version')

    def stop_header(self, *dummy):
        """
        Check the version of Gramps and XML.
        """
        if self.__gramps_version == 'unknown':
            msg = _("The .gramps file you are importing does not contain information about "
                    "the version of Gramps with, which it was produced.\n\n"
                    "The file will not be imported.")
            raise GrampsImportError(_('Import file misses Gramps version'), msg)
        if not re.match("\d+\.\d+\.\d+", self.__xml_version):
            msg = _("The .gramps file you are importing does not contain a "
                    "valid xml-namespace number.\n\n"
                    "The file will not be imported.")
            raise GrampsImportError(_('Import file contains unacceptable XML '
                    'namespace version'), msg)
        if self.__xml_version > libgrampsxml.GRAMPS_XML_VERSION:
            msg = _("The .gramps file you are importing was made by "
                    "version %(newer)s of "
                    "Gramps, while you are running an older version %(older)s. "
                    "The file will not be imported. Please upgrade to the "
                    "latest version of Gramps and try again." ) % {
                    'newer' : self.__gramps_version, 'older' : const.VERSION }
            raise GrampsImportError('', msg)
        if self.__xml_version < '1.0.0':
            msg = _("The .gramps file you are importing was made by version "
                    "%(oldgramps)s of Gramps, while you are running a more "
                    "recent version %(newgramps)s.\n\n"
                    "The file will not be imported. Please use an older version"
                    " of Gramps that supports version %(xmlversion)s of the "
                    "xml.\nSee\n  "
                    "http://gramps-project.org/wiki/index.php?title=GRAMPS_XML"
                    "\n for more info."
                    ) % {'oldgramps': self.__gramps_version, 
                        'newgramps': const.VERSION,
                        'xmlversion': self.__xml_version,
                        }
            raise GrampsImportError(_('The file will not be imported'), msg)
        elif self.__xml_version < '1.1.0':
            msg = _("The .gramps file you are importing was made by version "
                    "%(oldgramps)s of Gramps, while you are running a much "
                    "more recent version %(newgramps)s.\n\n"
                    "Ensure after import everything is imported correctly. In "
                    "the event of problems, please submit a bug and use an "
                    "older version of Gramps in the meantime to import this "
                    "file, which is version %(xmlversion)s of the xml.\nSee\n  "
                    "http://gramps-project.org/wiki/index.php?title=GRAMPS_XML"
                    "\nfor more info."
                    ) % {'oldgramps': self.__gramps_version, 
                        'newgramps': const.VERSION,
                        'xmlversion': self.__xml_version,
                        }
            WarningDialog(_('Old xml file'), msg)

    def start_lds_ord(self, attrs):
        self.ord = gen.lib.LdsOrd()
        self.ord.set_type_from_xml(attrs['type'])
        self.ord.private = bool(attrs.get("priv"))
        if self.person:
            self.person.lds_ord_list.append(self.ord)
        elif self.family:
            self.family.lds_ord_list.append(self.ord)

    def start_temple(self, attrs):
        self.ord.set_temple(attrs['val'])

    def start_data_item(self, attrs):
        self.source.set_data_item(attrs['key'], attrs['value'])

    def start_status(self, attrs):
        try:
            # old xml with integer statuses
            self.ord.set_status(int(attrs['val']))
        except ValueError:
            # string
            self.ord.set_status_from_xml(attrs['val'])

    def start_sealed_to(self, attrs):
        """
        Add a family reference to the LDS ordinance currently processed.
        """
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Family,
                                     self.db.has_family_handle,
                                     self.db.add_family,
                                     self.db.get_raw_family_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), FAMILY_KEY,
                                        gen.lib.Family)
        self.ord.set_family_handle(handle)
        
    def start_place(self, attrs):
        """A reference to a place in an object: event or lds_ord
        """
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Place,
                                     self.db.has_place_handle,
                                     self.db.add_place,
                                     self.db.get_raw_place_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), PLACE_KEY,
                                        gen.lib.Place)
        if self.ord:
            self.ord.set_place_handle(handle)
        elif self.event:
            self.event.set_place_handle(handle)
        
    def start_placeobj(self, attrs):
        """
        Add a place object to db if it doesn't exist yet and assign
        id, privacy and changetime.
        """
        self.placeobj = gen.lib.Place()
        if 'handle' in attrs:
            orig_handle = attrs['handle'].replace('_', '')
            is_merge_candidate = (self.replace_import_handle and
                                  self.db.has_place_handle(orig_handle))
            self.inaugurate(orig_handle, self.placeobj,
                            self.db.has_place_handle,
                            self.db.add_place,
                            self.db.get_raw_place_data)
            gramps_id = self.legalize_id(attrs.get('id'), PLACE_KEY,
                                         self.pidswap, self.db.pid2user_format,
                                         self.db.find_next_place_gramps_id)
            self.placeobj.set_gramps_id(gramps_id)
            if is_merge_candidate:
                orig_place = self.db.get_place_from_handle(orig_handle)
                self.info.add('merge-candidate', PLACE_KEY, orig_place,
                              self.placeobj)
        else:
            self.inaugurate_id(attrs.get('id'), PLACE_KEY, self.placeobj)
        self.placeobj.private = bool(attrs.get("priv"))
        self.placeobj.change = int(attrs.get('change', self.change))
        self.info.add('new-object', PLACE_KEY, self.placeobj)
        
        # GRAMPS LEGACY: title in the placeobj tag
        self.placeobj.title = attrs.get('title', '')
        self.locations = 0
        self.update(self.p.CurrentLineNumber)
            
    def start_location(self, attrs):
        """Bypass the function calls for this one, since it appears to
        take up quite a bit of time"""
        
        loc = gen.lib.Location()
        loc.street = attrs.get('street', '')
        loc.locality = attrs.get('locality', '')
        loc.city = attrs.get('city', '')
        loc.parish = attrs.get('parish', '')
        loc.county = attrs.get('county', '')
        loc.state = attrs.get('state', '')
        loc.country = attrs.get('country', '')
        loc.postal = attrs.get('postal', '')
        loc.phone = attrs.get('phone', '')
        if self.locations > 0:
            self.placeobj.add_alternate_locations(loc)
        else:
            self.placeobj.set_main_location(loc)
            self.locations = self.locations + 1

    def start_witness(self, attrs):
        """
        Add a note about a witness to the currently processed event or add
        an event reference connecting that event with a person assigning the
        role of witness.
        """
        # Parse witnesses created by older gramps
        self.in_witness = True
        self.witness_comment = ""
        if 'name' in attrs:
            note = gen.lib.Note()
            note.handle = Utils.create_id()
            note.set(_("Witness name: %s") % attrs['name'])
            note.type.set(gen.lib.NoteType.EVENT)
            note.private = self.event.private
            self.db.add_note(note, self.trans)
            #set correct change time
            self.db.commit_note(note, self.trans, self.change)
            self.info.add('new-object', NOTE_KEY, note)
            self.event.add_note(note.handle)
            return

        person = gen.lib.Person()
        if 'hlink' in attrs:
            self.inaugurate(attrs['hlink'], person, self.db.has_person_handle,
                            self.db.add_person, self.db.get_raw_person_data)
        elif 'ref' in attrs:
            self.inaugurate_id(attrs['ref'], PERSON_KEY, person)
        else:
            person = None

        # Add an EventRef from that person
        # to this event using ROLE_WITNESS role
        if person:
            event_ref = gen.lib.EventRef()
            event_ref.ref = self.event.handle
            event_ref.role.set(gen.lib.EventRoleType.WITNESS)
            person.event_ref_list.append(event_ref)
            self.db.commit_person(person, self.trans, self.change)
        
    def start_coord(self, attrs):
        self.placeobj.lat = attrs.get('lat', '')
        self.placeobj.long = attrs.get('long', '')

    def start_event(self, attrs):
        """
        Add an event object to db if it doesn't exist yet and assign
        id, privacy and changetime.
        """
        if self.person or self.family:
            # GRAMPS LEGACY: old events that were written inside
            # person or family objects.
            self.event = gen.lib.Event()
            self.event.handle = Utils.create_id()
            self.event.type = gen.lib.EventType()
            self.event.type.set_from_xml_str(attrs['type'])
            self.db.add_event(self.event, self.trans)
            #set correct change time
            self.db.commit_event(self.event, self.trans, self.change)
            self.info.add('new-object', EVENT_KEY, self.event)
        else:
            # This is new event, with ID and handle already existing
            self.update(self.p.CurrentLineNumber)
            self.event = gen.lib.Event()
            if 'handle' in attrs:
                orig_handle = attrs['handle'].replace('_', '')
                is_merge_candidate = (self.replace_import_handle and
                                      self.db.has_event_handle(orig_handle))
                self.inaugurate(orig_handle, self.event,
                                self.db.has_event_handle,
                                self.db.add_event,
                                self.db.get_raw_event_data)
                gramps_id = self.legalize_id(attrs.get('id'), EVENT_KEY,
                                          self.eidswap, self.db.eid2user_format,
                                          self.db.find_next_event_gramps_id)
                self.event.set_gramps_id(gramps_id)
                if is_merge_candidate:
                    orig_event = self.db.get_event_from_handle(orig_handle)
                    self.info.add('merge-candidate', EVENT_KEY, orig_event,
                                  self.event)
            else: #old style XML
                self.inaugurate_id(attrs.get('id'), EVENT_KEY, self.event)
            self.event.private = bool(attrs.get("priv"))
            self.event.change = int(attrs.get('change', self.change))
            self.info.add('new-object', EVENT_KEY, self.event)

    def start_eventref(self, attrs):
        """
        Add an event reference to the object currently processed.
        """
        self.eventref = gen.lib.EventRef()
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Event,
                                     self.db.has_event_handle,
                                     self.db.add_event,
                                     self.db.get_raw_event_data)
        else: # there is no old style XML
            raise GrampsImportError(_("The Gramps Xml you are trying to "
                "import is malformed."), _("Any event reference must have a "
                "'hlink' attribute."))
        self.eventref.ref = handle
        self.eventref.private = bool(attrs.get('priv'))
        if 'role' in attrs:
            self.eventref.role.set_from_xml_str(attrs['role'])

        # We count here on events being already parsed prior to parsing
        # people or families. This code will fail if this is not true.
        event = self.db.get_event_from_handle(self.eventref.ref)
        if not event:
            return
        
        if self.family:
            event.personal = False
            self.family.add_event_ref(self.eventref)
        elif self.person:
            event.personal = True
            if (event.type == gen.lib.EventType.BIRTH) \
                   and (self.eventref.role == gen.lib.EventRoleType.PRIMARY) \
                   and (self.person.get_birth_ref() is None):
                self.person.set_birth_ref(self.eventref)
            elif (event.type == gen.lib.EventType.DEATH) \
                     and (self.eventref.role == gen.lib.EventRoleType.PRIMARY) \
                     and (self.person.get_death_ref() is None):
                self.person.set_death_ref(self.eventref)
            else:
                self.person.add_event_ref(self.eventref)

    def start_attribute(self, attrs):
        self.attribute = gen.lib.Attribute()
        self.attribute.private = bool(attrs.get("priv"))
        self.attribute.type = gen.lib.AttributeType()
        if 'type' in attrs:
            self.attribute.type.set_from_xml_str(attrs["type"])
        self.attribute.value = attrs.get("value", '')
        if self.photo:
            self.photo.add_attribute(self.attribute)
        elif self.object:
            self.object.add_attribute(self.attribute)
        elif self.objref:
            self.objref.add_attribute(self.attribute)
        elif self.event:
            self.event.add_attribute(self.attribute)
        elif self.eventref:
            self.eventref.add_attribute(self.attribute)
        elif self.person:
            self.person.add_attribute(self.attribute)
        elif self.family:
            self.family.add_attribute(self.attribute)

    def start_address(self, attrs):
        self.address = gen.lib.Address()
        self.address.private = bool(attrs.get("priv"))

    def start_bmark(self, attrs):
        """
        Add a bookmark to db.
        """
        target = attrs.get('target')
        if not target:
            # Old XML. Can be either handle or id reference
            # and this is guaranteed to be a person bookmark
            if 'hlink' in attrs:
                handle = self.inaugurate(attrs['hlink'], gen.lib.Person,
                                         self.db.has_person_handle,
                                         self.db.add_person,
                                         self.db.get_raw_person_data)
            else:
                handle = self.inaugurate_id(attrs.get('ref'), PERSON_KEY,
                                            gen.lib.Person)
            self.db.bookmarks.append(handle)
            return

        # This is new XML, so we are guaranteed to have a handle ref
        handle = attrs['hlink'].replace('_', '')
        handle = self.import_handles[handle]
        # Due to pre 2.2.9 bug, bookmarks might be handle of other object
        # Make sure those are filtered out.
        # Bookmarks are at end, so all handle must exist before we do bookmrks
        if target == 'person':
            if (self.db.get_person_from_handle(handle) is not None
                    and handle not in self.db.bookmarks.get() ):
                self.db.bookmarks.append(handle)
        elif target == 'family':
            if (self.db.get_family_from_handle(handle) is not None
                    and handle not in self.db.family_bookmarks.get() ):
                self.db.family_bookmarks.append(handle)
        elif target == 'event':
            if (self.db.get_event_from_handle(handle) is not None
                    and handle not in self.db.event_bookmarks.get() ):
                self.db.event_bookmarks.append(handle)
        elif target == 'source':
            if (self.db.get_source_from_handle(handle) is not None
                    and handle not in self.db.source_bookmarks.get() ):
                self.db.source_bookmarks.append(handle)
        elif target == 'place':
            if (self.db.get_place_from_handle(handle) is not None
                    and handle not in self.db.place_bookmarks.get() ):
                self.db.place_bookmarks.append(handle)
        elif target == 'media':
            if (self.db.get_object_from_handle(handle) is not None
                    and handle not in self.db.media_bookmarks.get() ):
                self.db.media_bookmarks.append(handle)
        elif target == 'repository':
            if (self.db.get_repository_from_handle(handle) 
                    is not None and handle not in self.db.repo_bookmarks.get()):
                self.db.repo_bookmarks.append(handle)
        elif target == 'note':
            if (self.db.get_note_from_handle(handle) is not None
                    and handle not in self.db.note_bookmarks.get() ):
                self.db.note_bookmarks.append(handle)

    def start_format(self, attrs):
        number = int(attrs['number'])
        name = attrs['name']
        fmt_str = attrs['fmt_str']
        active = bool(attrs.get('active', True))

        if number in self.taken_name_format_numbers:
            number = self.remap_name_format(number)

        self.name_formats.append((number, name, fmt_str, active))

    def remap_name_format(self, old_number):
        if old_number in self.name_formats_map: # This should not happen
            return self.name_formats_map[old_number]
        # Find the lowest new number not taken yet:
        new_number = -1
        while new_number in self.taken_name_format_numbers:
            new_number -= 1
        # Add this to the taken list
        self.taken_name_format_numbers.append(new_number)
        # Set up the mapping entry
        self.name_formats_map[old_number] = new_number
        # Return new number
        return new_number
        
    def start_person(self, attrs):
        """
        Add a person to db if it doesn't exist yet and assign
        id, privacy and changetime.
        """
        self.update(self.p.CurrentLineNumber)
        self.person = gen.lib.Person()
        if 'handle' in attrs:
            orig_handle = attrs['handle'].replace('_', '')
            is_merge_candidate = (self.replace_import_handle and
                                  self.db.has_person_handle(orig_handle))
            self.inaugurate(orig_handle, self.person,
                            self.db.has_person_handle,
                            self.db.add_person,
                            self.db.get_raw_person_data)
            gramps_id = self.legalize_id(attrs.get('id'), PERSON_KEY,
                                        self.idswap, self.db.id2user_format,
                                        self.db.find_next_person_gramps_id)
            self.person.set_gramps_id(gramps_id)
            if is_merge_candidate:
                orig_person = self.db.get_person_from_handle(orig_handle)
                self.info.add('merge-candidate', PERSON_KEY, orig_person,
                              self.person)
        else: # old style XML
            self.inaugurate_id(attrs.get('id'), PERSON_KEY, self.person)
        self.person.private = bool(attrs.get("priv"))
        self.person.change = int(attrs.get('change', self.change))
        self.info.add('new-object', PERSON_KEY, self.person)
        self.convert_marker(attrs, self.person)

    def start_people(self, attrs):
        """
        Store the home person of the database.
        """
        if 'home' in attrs:
            handle = self.inaugurate(attrs['home'], gen.lib.Person,
                                     self.db.has_person_handle,
                                     self.db.add_person,
                                     self.db.get_raw_person_data)
            self.home = handle

    def start_father(self, attrs):
        """
        Add a father reference to the family currently processed.
        """
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Person,
                                    self.db.has_person_handle,
                                    self.db.add_person,
                                    self.db.get_raw_person_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), PERSON_KEY,
                                        gen.lib.Person)
        self.family.set_father_handle(handle)

    def start_mother(self, attrs):
        """
        Add a mother reference to the family currently processed.
        """
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Person,
                                    self.db.has_person_handle,
                                    self.db.add_person,
                                    self.db.get_raw_person_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), PERSON_KEY,
                                        gen.lib.Person)
        self.family.set_mother_handle(handle)
    
    def start_child(self, attrs):
        """
        Add a child reference to the family currently processed.

        Here we are handling the old XML, in which
        frel and mrel belonged to the "childof" tag
        """
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Person,
                                     self.db.has_person_handle,
                                     self.db.add_person,
                                     self.db.get_raw_person_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), PERSON_KEY,
                                        gen.lib.Person)

        # If that were the case then childref_map has the childref ready
        if (self.family.handle, handle) in self.childref_map:
            self.family.add_child_ref(
                self.childref_map[(self.family.handle, handle)])

    def start_childref(self, attrs):
        """
        Add a child reference to the family currently processed.

        Here we are handling the new XML, in which frel and mrel
        belong to the "childref" tag under family.
        """
        self.childref = gen.lib.ChildRef()
        handle = self.inaugurate(attrs['hlink'], gen.lib.Person,
                                 self.db.has_person_handle,
                                 self.db.add_person,
                                 self.db.get_raw_person_data)
        self.childref.ref = handle
        self.childref.private = bool(attrs.get('priv'))

        mrel = gen.lib.ChildRefType()
        if attrs.get('mrel'):
            mrel.set_from_xml_str(attrs['mrel'])
        frel = gen.lib.ChildRefType()
        if attrs.get('frel'):
            frel.set_from_xml_str(attrs['frel'])

        if not mrel.is_default():
            self.childref.set_mother_relation(mrel)
        if not frel.is_default():
            self.childref.set_father_relation(frel)
        self.family.add_child_ref(self.childref)

    def start_personref(self, attrs):
        """
        Add a person reference to the person currently processed.
        """
        self.personref = gen.lib.PersonRef()
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Person,
                                    self.db.has_person_handle,
                                    self.db.add_person,
                                    self.db.get_raw_person_data)
        else: # there is no old style XML
            raise GrampsImportError(_("The Gramps Xml you are trying to "
                "import is malformed."), _("Any person reference must have a "
                "'hlink' attribute."))
        self.personref.ref = handle
        self.personref.private = bool(attrs.get('priv'))
        self.personref.rel = attrs['rel']
        self.person.add_person_ref(self.personref)

    def start_url(self, attrs):
        if "href" not in attrs:
            return
        url = gen.lib.Url()
        url.path = attrs["href"]
        url.set_description(attrs.get("description", ''))
        url.private = bool(attrs.get('priv'))
        url.type.set_from_xml_str(attrs.get('type', ''))
        if self.person:
            self.person.add_url(url)
        elif self.placeobj:
            self.placeobj.add_url(url)
        elif self.repo:
            self.repo.add_url(url)

    def start_family(self, attrs):
        """
        Add a family object to db if it doesn't exist yet and assign
        id, privacy and changetime.
        """
        self.update(self.p.CurrentLineNumber)
        self.family = gen.lib.Family()
        if 'handle' in attrs:
            orig_handle = attrs['handle'].replace('_', '')
            is_merge_candidate = (self.replace_import_handle and
                                  self.db.has_family_handle(orig_handle))
            self.inaugurate(orig_handle, self.family,
                            self.db.has_family_handle,
                            self.db.add_family,
                            self.db.get_raw_family_data)
            gramps_id = self.legalize_id(attrs.get('id'), FAMILY_KEY,
                                        self.fidswap, self.db.fid2user_format,
                                        self.db.find_next_family_gramps_id)
            self.family.set_gramps_id(gramps_id)
            if is_merge_candidate:
                orig_family = self.db.get_family_from_handle(orig_handle)
                self.info.add('merge-candidate', FAMILY_KEY, orig_family,
                              self.family)
        else: # old style XML
            self.inaugurate_id(attrs.get('id'), FAMILY_KEY, self.family)
        self.family.private = bool(attrs.get("priv"))
        self.family.change = int(attrs.get('change', self.change))
        self.info.add('new-object', FAMILY_KEY, self.family)
        # GRAMPS LEGACY: the type now belongs to <rel> tag
        # Here we need to support old format of <family type="Married">
        if 'type' in attrs:
            self.family.type.set_from_xml_str(attrs["type"])
        self.convert_marker(attrs, self.family)

    def start_rel(self, attrs):
        if 'type' in attrs:
            self.family.type.set_from_xml_str(attrs["type"])

    def start_file(self, attrs):
        self.object.mime = attrs['mime']
        if 'description' in attrs:
            self.object.desc = attrs['description']
        else:
            self.object.desc = ""
        #keep value of path, no longer make absolute paths on import
        src = attrs["src"]
        if src:
            self.object.path = src
            if self.all_abs and not os.path.isabs(src):
                self.all_abs = False
                self.info.add('relative-path', None, None)

    def start_childof(self, attrs):
        """
        Add a family reference to the person currently processed in which that
        person is a child.
        """
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Family,
                                    self.db.has_family_handle,
                                    self.db.add_family,
                                    self.db.get_raw_family_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), FAMILY_KEY,
                                        gen.lib.Family)

        # Here we are handling the old XML, in which
        # frel and mrel belonged to the "childof" tag
        mrel = gen.lib.ChildRefType()
        frel = gen.lib.ChildRefType()
        if 'mrel' in attrs:
            mrel.set_from_xml_str(attrs['mrel'])
        if 'frel' in attrs:
            frel.set_from_xml_str(attrs['frel'])

        childref = gen.lib.ChildRef()
        childref.ref = self.person.handle
        if not mrel.is_default():
            childref.set_mother_relation(mrel)
        if not frel.is_default():
            childref.set_father_relation(frel)
        self.childref_map[(handle, self.person.handle)] = childref
        self.person.add_parent_family_handle(handle)

    def start_parentin(self, attrs):
        """
        Add a family reference to the person currently processed in which that
        person is a parent.
        """
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Family,
                                    self.db.has_family_handle,
                                    self.db.add_family,
                                    self.db.get_raw_family_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), FAMILY_KEY,
                                        gen.lib.Family)
        self.person.add_family_handle(handle)

    def start_name(self, attrs):
        if not self.in_witness:
            self.name = gen.lib.Name()
            name_type = attrs.get('type', "Birth Name")
            # Mapping "Other Name" from gramps 2.0.x to Unknown
            if (self.__xml_version == '1.0.0') and (name_type == 'Other Name'):
                self.name.set_type(gen.lib.NameType.UNKNOWN)
            else:
                self.name.type.set_from_xml_str(name_type)
            self.name.private = bool(attrs.get("priv", 0))
            self.alt_name = bool(attrs.get("alt", 0))
            try:
                sort_as = int(attrs["sort"])
                # check if these pointers need to be remapped
                # and set the name attributes
                if sort_as in self.name_formats_map:
                    self.name.sort_as = self.name_formats_map[sort_as]
                else:
                    self.name.sort_as = sort_as
            except KeyError:
                pass
            try:
                display_as = int(attrs["display"])
                # check if these pointers need to be remapped
                # and set the name attributes
                if display_as in self.name_formats_map:
                    self.name.display_as = self.name_formats_map[display_as]
                else:
                    self.name_display_as = display_as
            except KeyError:
                pass

    def start_surname(self, attrs):
        self.surname = gen.lib.Surname()
        self.surname.set_prefix(attrs.get("prefix", ""))
        self.surname.set_primary(bool(attrs.get("prim", 1)))
        self.surname.set_connector(attrs.get("connector", ""))
        origin_type = attrs.get("derivation", "")
        self.surname.origintype.set_from_xml_str(origin_type)

    def start_namemap(self, attrs):
        type = attrs.get('type')
        key = attrs['key']
        value = attrs['value']
        if type == 'group_as':
            if self.db.has_name_group_key(key) :
                present = self.db.get_name_group_mapping(key)
                if not value == present:
                    msg = _('Your family tree groups name "%(key)s" together'
                            ' with "%(parent)s", did not change this grouping to "%(value)s".') % {
                            'key' : key, 'parent' : present, 'value' : value }
                    WarningDialog(_("Gramps ignored namemap value"), msg)
            else:
                self.db.set_name_group_mapping(key, value)

    def start_last(self, attrs):
        """ This is the element in version < 1.4.0 to do the surname"""
        self.surname = gen.lib.Surname()
        self.surname.prefix = attrs.get('prefix', '')
        self.name.group_as = attrs.get('group', '')

    def start_patronymic(self, attrs):
        """ This is the element in version < 1.4.0 to do the patronymic"""
        self.surnamepat = gen.lib.Surname()
        self.surnamepat.set_origintype(gen.lib.NameOriginType(
                                       gen.lib.NameOriginType.PATRONYMIC))

    def start_style(self, attrs):
        """
        Styled text tag in notes (v1.4.0 onwards).
        """
        tagtype = gen.lib.StyledTextTagType()
        tagtype.set_from_xml_str(attrs['name'].lower())
        try:
            val = attrs['value']
            tagvalue = gen.lib.StyledTextTagType.STYLE_TYPE[int(tagtype)](val)
        except KeyError:
            tagvalue = None
        except ValueError:
            return
        
        self.note_tags.append(gen.lib.StyledTextTag(tagtype, tagvalue))

    def start_tag(self, attrs):
        """
        Tag definition.
        """
        if self.note is not None:
            # Styled text tag in notes (prior to v1.4.0)
            self.start_style(attrs)
            return

        # Tag defintion
        self.tag = gen.lib.Tag()
        self.inaugurate(attrs['handle'], self.tag, self.db.has_tag_handle,
                        self.db.add_tag, self.db.get_raw_tag_data)
        self.tag.change = int(attrs.get('change', self.change))
        self.info.add('new-object', TAG_KEY, self.tag)
        self.tag.set_name(attrs['name'])
        self.tag.set_color(attrs['color'])
        self.tag.set_priority(int(attrs['priority']))
        self.db.commit_tag(self.tag, self.trans, self.tag.get_change_time())

    def start_tagref(self, attrs):
        """
        Tag reference in a primary object.
        """
        handle = self.inaugurate(attrs['hlink'], gen.lib.Tag,
                                 self.db.has_tag_handle,
                                 self.db.add_tag, self.db.get_raw_tag_data)

        if self.person:
            self.person.add_tag(handle)

        if self.family:
            self.family.add_tag(handle)

        if self.object:
            self.object.add_tag(handle)

        if self.note:
            self.note.add_tag(handle)
    
    def start_range(self, attrs):
        self.note_tags[-1].ranges.append((int(attrs['start']),
                                          int(attrs['end'])))
        
    def start_note(self, attrs):
        """
        Add a note to db if it doesn't exist yet and assign
        id, privacy, changetime, format and type.
        """
        self.in_note = 0
        if 'handle' in attrs:
            # This is new note, with ID and handle already existing
            self.update(self.p.CurrentLineNumber)
            self.note = gen.lib.Note()
            if 'handle' in attrs:
                orig_handle = attrs['handle'].replace('_', '')
                is_merge_candidate = (self.replace_import_handle and
                                      self.db.has_note_handle(orig_handle))
                self.inaugurate(orig_handle, self.note,
                                self.db.has_note_handle,
                                self.db.add_note,
                                self.db.get_raw_note_data)
                gramps_id = self.legalize_id(attrs.get('id'), NOTE_KEY,
                                          self.nidswap, self.db.nid2user_format,
                                          self.db.find_next_note_gramps_id)
                self.note.set_gramps_id(gramps_id)
                if is_merge_candidate: 
                    orig_note = self.db.get_note_from_handle(orig_handle)
                    self.info.add('merge-candicate', NOTE_KEY, orig_note,
                                  self.note)
            else:
                self.inaugurate_id(attrs.get('id'), NOTE_KEY, self.note)
            self.note.private = bool(attrs.get("priv"))
            self.note.change = int(attrs.get('change', self.change))
            self.info.add('new-object', NOTE_KEY, self.note)
            self.note.format = int(attrs.get('format', gen.lib.Note.FLOWED))
            self.note.type.set_from_xml_str(attrs['type'])

            self.convert_marker(attrs, self.note)
            
            # Since StyledText was introduced (XML v1.3.0) the clear text
            # part of the note is moved between <text></text> tags.
            # To catch the different versions here we reset the note_text
            # variable. It will be checked in stop_note() then.
            self.note_text = None
            self.note_tags = []
        else:
            # GRAMPS LEGACY: old notes that were written inside other objects
            # We need to create a top-level note, it's type depends on 
            #   the caller object, and inherits privacy from caller object
            # On stop_note the reference to this note will be added
            self.note = gen.lib.Note()
            self.note.handle = Utils.create_id()
            self.note.format = int(attrs.get('format', gen.lib.Note.FLOWED))
            # The order in this long if-then statement should reflect the
            # DTD: most deeply nested elements come first.
            if self.source_ref:
                self.note.type.set(gen.lib.NoteType.SOURCEREF)
                self.note.private = self.source_ref.private
            elif self.address:
                self.note.type.set(gen.lib.NoteType.ADDRESS)
                self.note.private = self.address.private
            elif self.ord:
                self.note.type.set(gen.lib.NoteType.LDS)
                self.note.private = self.ord.private
            elif self.attribute:
                self.note.type.set(gen.lib.NoteType.ATTRIBUTE)
                self.note.private = self.attribute.private
            elif self.object:
                self.note.type.set(gen.lib.NoteType.MEDIA)
                self.note.private = self.object.private
            elif self.objref:
                self.note.type.set(gen.lib.NoteType.MEDIAREF)
                self.note.private = self.objref.private
            elif self.photo:
                self.note.type.set(gen.lib.NoteType.MEDIA)
                self.note.private = self.photo.private
            elif self.name:
                self.note.type.set(gen.lib.NoteType.PERSONNAME)
                self.note.private = self.name.private
            elif self.eventref:
                self.note.type.set(gen.lib.NoteType.EVENTREF)
                self.note.private = self.eventref.private
            elif self.reporef:
                self.note.type.set(gen.lib.NoteType.REPOREF)
                self.note.private = self.reporef.private
            elif self.source:
                self.note.type.set(gen.lib.NoteType.SOURCE)
                self.note.private = self.source.private
            elif self.event:
                self.note.type.set(gen.lib.NoteType.EVENT)
                self.note.private = self.event.private
            elif self.personref:
                self.note.type.set(gen.lib.NoteType.ASSOCIATION)
                self.note.private = self.personref.private
            elif self.person:
                self.note.type.set(gen.lib.NoteType.PERSON)
                self.note.private = self.person.private
            elif self.childref:
                self.note.type.set(gen.lib.NoteType.CHILDREF)
                self.note.private = self.childref.private
            elif self.family:
                self.note.type.set(gen.lib.NoteType.FAMILY)
                self.note.private = self.family.private
            elif self.placeobj:
                self.note.type.set(gen.lib.NoteType.PLACE)
                self.note.private = self.placeobj.private
            elif self.repo:
                self.note.type.set(gen.lib.NoteType.REPO)
                self.note.private = self.repo.private
 
            self.db.add_note(self.note, self.trans)
            #set correct change time
            self.db.commit_note(self.note, self.trans, self.change)
            self.info.add('new-object', NOTE_KEY, self.note)

    def start_noteref(self, attrs):
        """
        Add a note reference to the object currently processed.
        """
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Note,
                                     self.db.has_note_handle,
                                     self.db.add_note,
                                     self.db.get_raw_note_data)
        else:
            raise GrampsImportError(_("The Gramps Xml you are trying to "
                "import is malformed."), _("Any note reference must have a "
                "'hlink' attribute."))

        # The order in this long if-then statement should reflect the
        # DTD: most deeply nested elements come first.
        if self.source_ref:
            self.source_ref.add_note(handle)
        elif self.address:
            self.address.add_note(handle)
        elif self.ord:
            self.ord.add_note(handle)
        elif self.attribute:
            self.attribute.add_note(handle)
        elif self.object:
            self.object.add_note(handle)
        elif self.objref:
            self.objref.add_note(handle)
        elif self.photo:
            self.photo.add_note(handle)
        elif self.name:
            self.name.add_note(handle)
        elif self.eventref:
            self.eventref.add_note(handle)
        elif self.reporef:
            self.reporef.add_note(handle)
        elif self.source:
            self.source.add_note(handle)
        elif self.event:
            self.event.add_note(handle)
        elif self.personref:
            self.personref.add_note(handle)
        elif self.person:
            self.person.add_note(handle)
        elif self.childref:
            self.childref.add_note(handle)
        elif self.family:
            self.family.add_note(handle)
        elif self.placeobj:
            self.placeobj.add_note(handle)
        elif self.repo:
            self.repo.add_note(handle)

    def start_sourceref(self, attrs):
        """
        Add a source reference to the object currently processed.
        """
        self.source_ref = gen.lib.SourceRef()
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Source,
                                     self.db.has_source_handle,
                                     self.db.add_source,
                                     self.db.get_raw_source_data)
        else:
            handle = self.inaugurate_id(attrs.get('ref'), SOURCE_KEY,
                                        gen.lib.Source)
        self.source_ref.ref = handle
        self.source_ref.confidence = int(attrs.get("conf", self.conf))
        self.source_ref.private = bool(attrs.get("priv"))
        
        if self.photo:
            self.photo.add_source_reference(self.source_ref)
        elif self.ord:
            self.ord.add_source_reference(self.source_ref)
        elif self.attribute:
            self.attribute.add_source_reference(self.source_ref)
        elif self.object:
            self.object.add_source_reference(self.source_ref)
        elif self.objref:
            self.objref.add_source_reference(self.source_ref)
        elif self.event:
            self.event.add_source_reference(self.source_ref)
        elif self.address:
            self.address.add_source_reference(self.source_ref)
        elif self.name:
            self.name.add_source_reference(self.source_ref)
        elif self.placeobj:
            self.placeobj.add_source_reference(self.source_ref)
        elif self.childref:
            self.childref.add_source_reference(self.source_ref)
        elif self.family:
            self.family.add_source_reference(self.source_ref)
        elif self.personref:
            self.personref.add_source_reference(self.source_ref)
        elif self.person:
            self.person.add_source_reference(self.source_ref)

    def start_source(self, attrs):
        """
        Add a source object to db if it doesn't exist yet and assign
        id, privacy and changetime.
        """
        self.update(self.p.CurrentLineNumber)
        self.source = gen.lib.Source()
        if 'handle' in attrs:
            orig_handle = attrs['handle'].replace('_', '')
            is_merge_candidate = (self.replace_import_handle and
                                  self.db.has_source_handle(orig_handle))
            self.inaugurate(orig_handle, self.source,
                            self.db.has_source_handle,
                            self.db.add_source,
                            self.db.get_raw_source_data)
            gramps_id = self.legalize_id(attrs.get('id'), SOURCE_KEY,
                                         self.sidswap, self.db.sid2user_format,
                                         self.db.find_next_source_gramps_id)
            self.source.set_gramps_id(gramps_id)
            if is_merge_candidate:
                orig_source = self.db.get_source_from_handle(orig_handle)
                self.info.add('merge-candidate', SOURCE_KEY, orig_source,
                              self.source)
        else: # old style XML
            self.inaugurate_id(attrs.get('id'), SOURCE_KEY, self.source)
        self.source.private = bool(attrs.get("priv"))
        self.source.change = int(attrs.get('change', self.change))
        self.info.add('new-object', SOURCE_KEY, self.source)

    def start_reporef(self, attrs):
        """
        Add a repository reference to the source currently processed.
        """
        self.reporef = gen.lib.RepoRef()
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.Repository,
                                     self.db.has_repository_handle,
                                     self.db.add_repository,
                                     self.db.get_raw_repository_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), REPOSITORY_KEY,
                                        gen.lib.Repository)
        self.reporef.ref = handle
        self.reporef.call_number = attrs.get('callno', '')
        if 'medium' in attrs:
            self.reporef.media_type.set_from_xml_str(attrs['medium'])
        self.reporef.private = bool(attrs.get("priv"))
        # we count here on self.source being available
        # reporefs can only be found within source
        self.source.add_repo_reference(self.reporef)

    def start_objref(self, attrs):
        """
        Add a media object reference to the object currently processed.
        """
        self.objref = gen.lib.MediaRef()
        if 'hlink' in attrs:
            handle = self.inaugurate(attrs['hlink'], gen.lib.MediaObject,
                                     self.db.has_object_handle,
                                     self.db.add_object,
                                     self.db.get_raw_object_data)
        else: # old style XML
            handle = self.inaugurate_id(attrs.get('ref'), MEDIA_KEY,
                                        gen.lib.MediaObject)
        self.objref.ref = handle
        self.objref.private = bool(attrs.get('priv'))
        if self.event:
            self.event.add_media_reference(self.objref)
        elif self.family:
            self.family.add_media_reference(self.objref)
        elif self.source:
            self.source.add_media_reference(self.objref)
        elif self.person:
            self.person.add_media_reference(self.objref)
        elif self.placeobj:
            self.placeobj.add_media_reference(self.objref)

    def start_region(self, attrs):
        rect = (int(attrs.get('corner1_x')),
                int(attrs.get('corner1_y')),
                int(attrs.get('corner2_x')),
                int(attrs.get('corner2_y')) )
        self.objref.set_rectangle(rect)

    def start_object(self, attrs):
        """
        Add a media object to db if it doesn't exist yet and assign
        id, privacy and changetime.
        """
        self.object = gen.lib.MediaObject()
        if 'handle' in attrs:
            orig_handle = attrs['handle'].replace('_', '')
            is_merge_candidate = (self.replace_import_handle and
                                  self.db.has_object_handle(orig_handle))
            self.inaugurate(orig_handle, self.object,
                            self.db.has_object_handle,
                            self.db.add_object,
                            self.db.get_raw_object_data)
            gramps_id = self.legalize_id(attrs.get('id'), MEDIA_KEY,
                                         self.oidswap, self.db.oid2user_format,
                                         self.db.find_next_object_gramps_id)
            self.object.set_gramps_id(gramps_id)
            if is_merge_candidate:
                orig_object = self.db.get_object_from_handle(orig_handle)
                self.info.add('merge-candidate', MEDIA_KEY, orig_object,
                              self.object)
        else:
            self.inaugurate_id(attrs.get('id'), MEDIA_KEY, self.object)
        self.object.private = bool(attrs.get("priv"))
        self.object.change = int(attrs.get('change', self.change))
        self.info.add('new-object', MEDIA_KEY, self.object)

        # GRAMPS LEGACY: src, mime, and description attributes
        # now belong to the <file> tag. Here we are supporting
        # the old format of <object src="blah"...>
        self.object.mime = attrs.get('mime', '')
        self.object.desc = attrs.get('description', '')
        src = attrs.get("src", '')
        if src:
            self.object.path = src

    def start_repo(self, attrs):
        """
        Add a repository to db if it doesn't exist yet and assign
        id, privacy and changetime.
        """
        self.repo = gen.lib.Repository()
        if 'handle' in attrs:
            orig_handle = attrs['handle'].replace('_', '')
            is_merge_candidate = (self.replace_import_handle and
                                  self.db.has_repository_handle(orig_handle))
            self.inaugurate(orig_handle, self.repo,
                            self.db.has_repository_handle,
                            self.db.add_repository,
                            self.db.get_raw_repository_data)
            gramps_id = self.legalize_id(attrs.get('id'), REPOSITORY_KEY,
                                         self.ridswap, self.db.rid2user_format,
                                         self.db.find_next_repository_gramps_id)
            self.repo.set_gramps_id(gramps_id)
            if is_merge_candidate:
                orig_repo = self.db.get_repository_from_handle(orig_handle)
                self.info.add('merge-candidate', REPOSITORY_KEY, orig_repo,
                              self.repo)
        else: # old style XML 
            self.inaugurate_id(attrs.get('id'), REPOSITORY_KEY, self.repo)
        self.repo.private = bool(attrs.get("priv"))
        self.repo.change = int(attrs.get('change', self.change))
        self.info.add('new-object', REPOSITORY_KEY, self.repo)

    def stop_people(self, *tag):
        pass

    def stop_database(self, *tag):
        self.update(self.p.CurrentLineNumber)

    def stop_object(self, *tag):
        self.db.commit_media_object(self.object, self.trans, 
                                    self.object.get_change_time())
        self.object = None

    def stop_objref(self, *tag):
        self.objref = None
        
    def stop_repo(self, *tag):
        self.db.commit_repository(self.repo, self.trans, 
                                  self.repo.get_change_time())
        self.repo = None

    def stop_reporef(self, *tag):
        self.reporef = None
        
    def start_photo(self, attrs):
        self.photo = gen.lib.MediaObject()
        self.pref = gen.lib.MediaRef()
        self.pref.set_reference_handle(self.photo.get_handle())
        
        for key in attrs.keys():
            if key == "descrip" or key == "description":
                self.photo.set_description(attrs[key])
            elif key == "priv":
                self.pref.set_privacy(int(attrs[key]))
                self.photo.set_privacy(int(attrs[key]))
            elif key == "src":
                src = attrs["src"]
                self.photo.set_path(src)
            else:
                attr = gen.lib.Attribute()
                attr.set_type(key)
                attr.set_value(attrs[key])
                self.photo.add_attribute(attr)
        self.photo.set_mime_type(gen.mime.get_type(self.photo.get_path()))
        self.db.add_object(self.photo, self.trans)
        #set correct change time
        self.db.commit_media_object(self.photo, self.trans, self.change)
        self.info.add('new-object', MEDIA_KEY, self.photo)
        if self.family:
            self.family.add_media_reference(self.pref)
        elif self.source:
            self.source.add_media_reference(self.pref)
        elif self.person:
            self.person.add_media_reference(self.pref)
        elif self.placeobj:
            self.placeobj.add_media_reference(self.pref)

    def start_daterange(self, attrs):
        self.start_compound_date(attrs, gen.lib.Date.MOD_RANGE)

    def start_datespan(self, attrs):
        self.start_compound_date(attrs, gen.lib.Date.MOD_SPAN)

    def start_compound_date(self, attrs, mode):
        if self.source_ref:
            date_value = self.source_ref.get_date_object()
        elif self.ord:
            date_value = self.ord.get_date_object()
        elif self.object:
            date_value = self.object.get_date_object()
        elif self.address:
            date_value = self.address.get_date_object()
        elif self.name:
            date_value = self.name.get_date_object()
        else:
            date_value = self.event.get_date_object()

        start = attrs['start'].split('-')
        stop  = attrs['stop'].split('-')

        try:
            year = int(start[0])
        except ValueError:
            year = 0

        try:
            month = int(start[1])
        except:
            month = 0

        try:
            day = int(start[2])
        except:
            day = 0

        try:
            rng_year = int(stop[0])
        except:
            rng_year = 0

        try:
            rng_month = int(stop[1])
        except:
            rng_month = 0

        try:
            rng_day = int(stop[2])
        except:
            rng_day = 0

        if "cformat" in attrs:
            cal = gen.lib.Date.calendar_names.index(attrs['cformat'])
        else:
            cal = gen.lib.Date.CAL_GREGORIAN

        if 'quality' in attrs:
            val = attrs['quality']
            if val == 'estimated':
                qual = gen.lib.Date.QUAL_ESTIMATED
            elif val == 'calculated':
                qual = gen.lib.Date.QUAL_CALCULATED
            else:
                qual = gen.lib.Date.QUAL_NONE
        else:
            qual = gen.lib.Date.QUAL_NONE
        
        dualdated = False
        if 'dualdated' in attrs:
            val = attrs['dualdated']
            if val == "1":
                dualdated = True

        newyear = gen.lib.Date.NEWYEAR_JAN1
        if 'newyear' in attrs:
            newyear = attrs['newyear']
            if newyear.isdigit():
                newyear = int(newyear)
            else:
                newyear = gen.lib.Date.newyear_to_code(newyear)

        date_value.set(qual, mode, cal, 
                       (day, month, year, dualdated, 
                        rng_day, rng_month, rng_year, dualdated), 
                       newyear=newyear)

    def start_dateval(self, attrs):
        if self.source_ref:
            date_value = self.source_ref.get_date_object()
        elif self.ord:
            date_value = self.ord.get_date_object()
        elif self.object:
            date_value = self.object.get_date_object()
        elif self.address:
            date_value = self.address.get_date_object()
        elif self.name:
            date_value = self.name.get_date_object()
        else:
            date_value = self.event.get_date_object()

        bce = 1
        val = attrs['val']
        if val[0] == '-':
            bce = -1
            val = val[1:]
        start = val.split('-')
        try:
            year = int(start[0])*bce
        except:
            year = 0

        try:
            month = int(start[1])
        except:
            month = 0

        try:
            day = int(start[2])
        except:
            day = 0

        if "cformat" in attrs:
            cal = gen.lib.Date.calendar_names.index(attrs['cformat'])
        else:
            cal = gen.lib.Date.CAL_GREGORIAN

        if 'type' in attrs:
            val = attrs['type']
            if val == "about":
                mod = gen.lib.Date.MOD_ABOUT
            elif val == "after":
                mod = gen.lib.Date.MOD_AFTER
            else:
                mod = gen.lib.Date.MOD_BEFORE
        else:
            mod = gen.lib.Date.MOD_NONE

        if 'quality' in attrs:
            val = attrs['quality']
            if val == 'estimated':
                qual = gen.lib.Date.QUAL_ESTIMATED
            elif val == 'calculated':
                qual = gen.lib.Date.QUAL_CALCULATED
            else:
                qual = gen.lib.Date.QUAL_NONE
        else:
            qual = gen.lib.Date.QUAL_NONE

        dualdated = False
        if 'dualdated' in attrs:
            val = attrs['dualdated']
            if val == "1":
                dualdated = True

        newyear = gen.lib.Date.NEWYEAR_JAN1
        if 'newyear' in attrs:
            newyear = attrs['newyear']
            if newyear.isdigit():
                newyear = int(newyear)
            else:
                newyear = gen.lib.Date.newyear_to_code(newyear)

        date_value.set(qual, mod, cal, (day, month, year, dualdated), 
                       newyear=newyear)

    def start_datestr(self, attrs):
        if self.source_ref:
            date_value = self.source_ref.get_date_object()
        elif self.ord:
            date_value = self.ord.get_date_object()
        elif self.object:
            date_value = self.object.get_date_object()
        elif self.address:
            date_value = self.address.get_date_object()
        elif self.name:
            date_value = self.name.get_date_object()
        else:
            date_value = self.event.get_date_object()

        date_value.set_as_text(attrs['val'])

    def start_pos(self, attrs):
        self.person.position = (int(attrs["x"]), int(attrs["y"]))

    def stop_attribute(self, *tag):
        self.attribute = None

    def stop_comment(self, tag):
        # Parse witnesses created by older gramps
        if tag.strip():
            self.witness_comment = tag
        else:
            self.witness_comment = ""

    def stop_witness(self, tag):
        # Parse witnesses created by older gramps
        if self.witness_comment:
            text = self.witness_comment
        elif tag.strip():
            text = tag
        else:
            text = None

        if text is not None:
            note = gen.lib.Note()
            note.handle = Utils.create_id()
            note.set(_("Witness comment: %s") % text)
            note.type.set(gen.lib.NoteType.EVENT)
            note.private = self.event.private
            self.db.add_note(note, self.trans)
            #set correct change time
            self.db.commit_note(note, self.trans, self.change)
            self.info.add('new-object', NOTE_KEY, note)
            self.event.add_note(note.handle)
        self.in_witness = False

    def stop_attr_type(self, tag):
        self.attribute.set_type(tag)

    def stop_attr_value(self, tag):
        self.attribute.set_value(tag)

    def stop_address(self, *tag):
        if self.person:
            self.person.add_address(self.address)
        elif self.repo:
            self.repo.add_address(self.address)
        self.address = None
        
    def stop_places(self, *tag):
        self.placeobj = None

    def stop_photo(self, *tag):
        self.photo = None

    def stop_ptitle(self, tag):
        self.placeobj.title = tag

    def stop_placeobj(self, *tag):
        if self.placeobj.title == "":
            loc = self.placeobj.get_main_location()
            self.placeobj.title = build_place_title(loc)

        self.db.commit_place(self.placeobj, self.trans, 
                             self.placeobj.get_change_time())
        self.placeobj = None

    def stop_family(self, *tag):
        self.db.commit_family(self.family, self.trans,
                              self.family.get_change_time())
        self.family = None
        
    def stop_type(self, tag):
        if self.event:
            # Event type
            self.event.type.set_from_xml_str(tag)
        elif self.repo:
            # Repository type
            self.repo.type.set_from_xml_str(tag)

    def stop_childref(self, tag):
        self.childref = None

    def stop_personref(self, tag):
        self.personref = None

    def stop_eventref(self, tag):
        self.eventref = None

    def stop_event(self, *tag):
        if self.family:
            ref = gen.lib.EventRef()
            ref.ref = self.event.handle
            ref.private = self.event.private
            ref.role.set(gen.lib.EventRoleType.FAMILY)
            self.family.add_event_ref(ref)
        elif self.person:
            ref = gen.lib.EventRef()
            ref.ref = self.event.handle
            ref.private = self.event.private
            ref.role.set(gen.lib.EventRoleType.PRIMARY)
            if (self.event.type == gen.lib.EventType.BIRTH) \
                   and (self.person.get_birth_ref() is None):
                self.person.set_birth_ref(ref)
            elif (self.event.type == gen.lib.EventType.DEATH) \
                     and (self.person.get_death_ref() is None):
                self.person.set_death_ref(ref)
            else:
                self.person.add_event_ref(ref)

        if self.event.get_description() == "" and \
               self.event.get_type() != gen.lib.EventType.CUSTOM:
            if self.family:
                text = EVENT_FAMILY_STR % {
                    'event_name' : str(self.event.get_type()), 
                    'family' : Utils.family_name(self.family, self.db), 
                    }
            elif self.person:
                text = EVENT_PERSON_STR % {
                    'event_name' : str(self.event.get_type()), 
                    'person' : name_displayer.display(self.person), 
                    }
            else:
                text = u''
            self.event.set_description(text)

        self.db.commit_event(self.event, self.trans,
                             self.event.get_change_time())
        self.event = None

    def stop_name(self, tag):
        if self.in_witness:
            # Parse witnesses created by older gramps
            note = gen.lib.Note()
            note.handle = Utils.create_id()
            note.set(_("Witness name: %s") % tag)
            note.type.set(gen.lib.NoteType.EVENT)
            note.private = self.event.private
            self.db.add_note(note, self.trans)
            #set correct change time
            self.db.commit_note(note, self.trans, self.change)
            self.info.add('new-object', NOTE_KEY, note)
            self.event.add_note(note.handle)
        else: 
            #first correct old xml that has no nametype set
            if self.alt_name:
                # alternate name or former aka tag 
                if self.name.get_type() == "":
                    self.name.set_type(gen.lib.NameType.AKA)
            else:
                if self.name.get_type() == "":
                    self.name.set_type(gen.lib.NameType.BIRTH)
                
            #same logic as bsddb upgrade for xml < 1.4.0 which will 
            #have a surnamepat and/or surname. From 1.4.0 surname has been
            #added to name in self.stop_surname
            if not self.surnamepat:
                #no patronymic, only add surname if present
                if self.surname:
                    self.name.add_surname(self.surname)
                    self.name.set_primary_surname(0)
            else:
                #a patronymic, if no surname, a single surname
                if not self.surname:
                    self.name.add_surname(self.surnamepat)
                    self.name.set_primary_surname(0)
                else:
                    #two surnames, first patronymic, then surname which is primary
                    self.name.add_surname(self.surnamepat)
                    self.name.add_surname(self.surname)
                    self.name.set_primary_surname(1)
            if self.alt_name:
                self.person.add_alternate_name(self.name)
            else:
                self.person.set_primary_name(self.name)
        self.name = None
        self.surname = None
        self.surnamepat = None

    def stop_aka(self, tag):
        if self.name.get_type() == "":
            self.name.set_type(gen.lib.NameType.AKA)
        if not self.surnamepat:
            #no patronymic, only add surname if present
            if self.surname:
                self.name.add_surname(self.surname)
                self.name.set_primary_surname(0)
        else:
            #a patronymic, if no surname, a single surname
            if not self.surname:
                self.name.add_surname(self.surnamepat)
                self.name.set_primary_surname(0)
            else:
                #two surnames, first patronymic, then surname which is primary
                self.name.add_surname(self.surnamepat)
                self.name.add_surname(self.surname)
                self.name.set_primary_surname(1)
        self.person.add_alternate_name(self.name)
        self.name = None

    def stop_rname(self, tag):
        # Repository name
        self.repo.name = tag

    def stop_ref(self, tag):
        """
        Parse witnesses created by older gramps
        """
        person = gen.lib.Person()
        self.inaugurate_id(tag, PERSON_KEY, person)
        # Add an EventRef from that person
        # to this event using ROLE_WITNESS role
        event_ref = gen.lib.EventRef()
        event_ref.ref = self.event.handle
        event_ref.role.set(gen.lib.EventRoleType.WITNESS)
        person.event_ref_list.append(event_ref)
        self.db.commit_person(person, self.trans, self.change)

    def stop_place(self, tag):
        """end of a reference to place, should do nothing ...
           Note, if we encounter <place>blabla</place> this method is called
                with tag='blabla
        """
        ##place = None
        ##handle = None
        ##if self.place_ref is None:  #todo, add place_ref in start and init
        ##    #legacy cody? I see no reason for this, but it was present
        ##    if tag in self.place_map:
        ##        place = self.place_map[tag]
        ##        handle = place.get_handle()
        ##        place = None
        ##    else:
        ##        place = RelLib.Place()
        ##        place.set_title(tag)
        ##        handle = place.get_handle()
        ##    if self.ord:
        ##        self.ord.set_place_handle(handle)
        ##    elif self.object:
        ##        self.object.set_place_handle(handle)
        ##    else:
        ##        self.event.set_place_handle(handle)
        ##    if place :
        ##        self.db.commit_place(self.placeobj,self.trans,self.change)
        ##self.place_ref = None
        pass
        
    def stop_date(self, tag):
        if tag:
            if self.address:
                DateHandler.set_date(self.address, tag)
            else:
                DateHandler.set_date(self.event, tag)

    def stop_first(self, tag):
        self.name.set_first_name(tag)

    def stop_call(self, tag):
        self.name.set_call_name(tag)

    def stop_families(self, *tag):
        self.family = None

    def stop_person(self, *tag):
        self.db.commit_person(self.person, self.trans,
                              self.person.get_change_time())
        self.person = None

    def stop_description(self, tag):
        self.event.set_description(tag)

    def stop_cause(self, tag):
        # The old event's cause is now an attribute
        attr = gen.lib.Attribute()
        attr.set_type(gen.lib.AttributeType.CAUSE)
        attr.set_value(tag)
        self.event.add_attribute(attr)

    def stop_gender(self, tag):
        t = tag
        if t == "M":
            self.person.set_gender (gen.lib.Person.MALE)
        elif t == "F":
            self.person.set_gender (gen.lib.Person.FEMALE)
        else:
            self.person.set_gender (gen.lib.Person.UNKNOWN)

    def stop_stitle(self, tag):
        self.source.title = tag

    def stop_sourceref(self, *tag):
        self.source_ref = None

    def stop_source(self, *tag):
        self.db.commit_source(self.source, self.trans,
                              self.source.get_change_time())
        self.source = None

    def stop_sauthor(self, tag):
        self.source.author = tag

    def stop_phone(self, tag):
        self.address.phone = tag

    def stop_street(self, tag):
        self.address.street = tag

    def stop_locality(self, tag):
        self.address.locality = tag

    def stop_city(self, tag):
        self.address.city = tag

    def stop_county(self, tag):
        self.address.county = tag

    def stop_state(self, tag):
        self.address.state = tag
        
    def stop_country(self, tag):
        self.address.country = tag

    def stop_postal(self, tag):
        self.address.set_postal_code(tag)

    def stop_spage(self, tag):
        self.source_ref.set_page(tag)

    def stop_lds_ord(self, *tag):
        self.ord = None

    def stop_spubinfo(self, tag):
        self.source.set_publication_info(tag)

    def stop_sabbrev(self, tag):
        self.source.set_abbreviation(tag)
        
    def stop_stext(self, tag):
        if self.use_p:
            self.use_p = 0
            text = fix_spaces(self.stext_list)
        else:
            text = tag
        # This is old XML. We no longer have "text" attribute in soure_ref.
        # So we create a new note, commit, and add the handle to note list.
        note = gen.lib.Note()
        note.handle = Utils.create_id()
        note.private = self.source_ref.private
        note.set(text)
        note.type.set(gen.lib.NoteType.SOURCE_TEXT)
        self.db.add_note(note, self.trans)   
        #set correct change time
        self.db.commit_note(note, self.trans, self.change)
        self.info.add('new-object', NOTE_KEY, note) 
        self.source_ref.add_note(note.handle)

    def stop_scomments(self, tag):
        if self.use_p:
            self.use_p = 0
            text = fix_spaces(self.scomments_list)
        else:
            text = tag
        note = gen.lib.Note()
        note.handle = Utils.create_id()
        note.private = self.source_ref.private
        note.set(text)
        note.type.set(gen.lib.NoteType.SOURCEREF)
        self.db.add_note(note, self.trans)
        #set correct change time
        self.db.commit_note(note, self.trans, self.change)
        self.info.add('new-object', NOTE_KEY, note)
        self.source_ref.add_note(note.handle)

    def stop_last(self, tag):
        if self.surname:
            self.surname.set_surname(tag)
        if not tag.strip() and not self.surname.get_prefix().strip():
            #consider empty surname as no surname
            self.surname = None

    def stop_surname(self, tag):
        if self.name:
            self.surname.set_surname(tag)
            self.name.add_surname(self.surname)
        self.surname = None

    def stop_group(self, tag):
        """ group name of a name"""
        if self.name:
            self.name.set_group_as(tag)

    def stop_suffix(self, tag):
        if self.name:
            self.name.set_suffix(tag)

    def stop_patronymic(self, tag):
        if self.surnamepat:
            self.surnamepat.set_surname(tag)
        if not tag.strip():
            self.surnamepat = None

    def stop_title(self, tag):
        if self.name:
            self.name.set_title(tag)

    def stop_nick(self, tag):
        """in < 1.3.0 nick is on person and mapped to attribute
           from 1.4.0 it is a name element
        """
        if self.name:
            self.name.set_nick_name(tag)
        elif self.person:
            attr = gen.lib.Attribute()
            attr.set_type(gen.lib.AttributeType.NICKNAME)
            attr.set_value(tag)
            self.person.add_attribute(attr)

    def stop_familynick(self, tag):
        if self.name:
            self.name.set_family_nick_name(tag)

    def stop_text(self, tag):
        self.note_text = tag
        
    def stop_note(self, tag):
        self.in_note = 0
        if self.use_p:
            self.use_p = 0
            text = fix_spaces(self.note_list)
        elif self.note_text is not None:
            text = self.note_text
        else:
            text = tag
            
        self.note.set_styledtext(gen.lib.StyledText(text, self.note_tags))

        # The order in this long if-then statement should reflect the
        # DTD: most deeply nested elements come first.
        if self.address:
            self.address.add_note(self.note.handle)
        elif self.ord:
            self.ord.add_note(self.note.handle)
        elif self.attribute:
            self.attribute.add_note(self.note.handle)
        elif self.object:
            self.object.add_note(self.note.handle)
        elif self.objref:
            self.objref.add_note(self.note.handle)
        elif self.photo:
            self.photo.add_note(self.note.handle)
        elif self.name:
            self.name.add_note(self.note.handle)
        elif self.eventref:
            self.eventref.add_note(self.note.handle)
        elif self.reporef:
            self.reporef.add_note(self.note.handle)
        elif self.source:
            self.source.add_note(self.note.handle)
        elif self.event:
            self.event.add_note(self.note.handle)
        elif self.personref:
            self.personref.add_note(self.note.handle)
        elif self.person:
            self.person.add_note(self.note.handle)
        elif self.childref:
            self.childref.add_note(self.note.handle)
        elif self.family:
            self.family.add_note(self.note.handle)
        elif self.placeobj:
            self.placeobj.add_note(self.note.handle)
        elif self.repo:
            self.repo.add_note(self.note.handle)

        self.db.commit_note(self.note, self.trans, self.note.get_change_time())
        self.note = None

    def stop_research(self, tag):
        self.owner.set_name(self.resname)
        self.owner.set_address(self.resaddr)
        self.owner.set_locality(self.reslocality)
        self.owner.set_city(self.rescity)
        self.owner.set_state(self.resstate)
        self.owner.set_country(self.rescon)
        self.owner.set_postal_code(self.respos)
        self.owner.set_phone(self.resphone)
        self.owner.set_email(self.resemail)

    def stop_resname(self, tag):
        self.resname = tag

    def stop_resaddr(self, tag):
        self.resaddr = tag

    def stop_reslocality(self, tag):
        self.reslocality = tag

    def stop_rescity(self, tag):
        self.rescity = tag

    def stop_resstate(self, tag):
        self.resstate = tag

    def stop_rescountry(self, tag):
        self.rescon = tag

    def stop_respostal(self, tag):
        self.respos = tag

    def stop_resphone(self, tag):
        self.resphone = tag

    def stop_resemail(self, tag):
        self.resemail = tag

    def stop_mediapath(self, tag):
        self.mediapath = tag

    def stop_ptag(self, tag):
        self.use_p = 1
        if self.in_note:
            self.note_list.append(tag)
        elif self.in_stext:
            self.stext_list.append(tag)
        elif self.in_scomments:
            self.scomments_list.append(tag)

    def startElement(self, tag, attrs):
        self.func_list[self.func_index] = (self.func, self.tlist)
        self.func_index += 1
        self.tlist = []

        try:
            f, self.func = self.func_map[tag]
            if f:
                f(attrs)
        except KeyError:
            self.func_map[tag] = (None, None)
            self.func = None

    def endElement(self, tag):
        if self.func:
            self.func(''.join(self.tlist))
        self.func_index -= 1    
        self.func, self.tlist = self.func_list[self.func_index]
        
    def characters(self, data):
        if self.func:
            self.tlist.append(data)

    def convert_marker(self, attrs, obj):
        """
        Convert markers into tags.
        
        Old and new markers: complete=1 and marker=word
        """
        if attrs.get('complete'): # this is only true for complete=1
            tag_name = 'Complete'
        else:
            tag_name = attrs.get('marker')

        if tag_name is not None:
            tag_name = _(tag_name)
            tag = self.db.get_tag_from_name(tag_name)
            if tag is None:
                tag = gen.lib.Tag()
                tag.set_name(tag_name)
                tag.set_priority(self.db.get_number_of_tags())
                tag_handle = self.db.add_tag(tag, self.trans)
            else:
                tag_handle = tag.get_handle()
            obj.add_tag(tag_handle)

def append_value(orig, val):
    if orig:
        return "%s, %s" % (orig, val)
    else:
        return val

def build_place_title(loc):
    "Builds a title from a location"
    value = ""
    if loc.parish:
        value = loc.parish
    if loc.city:
        value = append_value(value, loc.city)
    if loc.county:
        value = append_value(value, loc.county)
    if loc.state:
        value = append_value(value, loc.state)
    if loc.country:
        value = append_value(value, loc.country)
    return value

