Hebrew Relationship Calculator

From Gramps
Revision as of 08:17, 7 September 2023 by Avma (talk | contribs) (Created page with "# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2003-2005 Donald N. Allingham # # This program is free software; you can redistribute it and/or modify # i...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
  1. Gramps - a GTK+/GNOME based genealogy program
  2. Copyright (C) 2003-2005 Donald N. Allingham
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

""" Classes for relationships. """

  1. -------------------------------------------------------------------------
  2. Python modules
  3. -------------------------------------------------------------------------

import logging

  1. -------------------------------------------------------------------------
  2. Gramps modules
  3. -------------------------------------------------------------------------

from gramps.gen.lib import Person, ChildRefType, EventType, FamilyRelType import gramps.gen.relationship from gramps.gen.plug import PluginRegister, BasePluginManager from gramps.gen.const import GRAMPS_LOCALE as glocale

_ = glocale.translation.sgettext

MALE = Person.MALE FEMALE = Person.FEMALE UNKNOWN = Person.UNKNOWN

LOG = logging.getLogger("gen.relationship") LOG.addHandler(logging.StreamHandler())

  1. -------------------------------------------------------------------------
  2. -------------------------------------------------------------------------

_LEVEL_NAME = [

   "",
   "מרמה ראשונה",
   "מרמה שניה",
   "מרמה שלישית",
   "מרמה רביעית",
   "מרמה חמישית",
   "מרמה שישית",
   "מרמה שביעית",
   "מרמה שמינית",
   "מרמה תשיעית",
   "מרמה עשירית",
   "מרמה אחת עשרה",

]

_REMOVED_LEVEL = [

   "",
    "מדרגה שניה",
    "פעמיים מדרגה שניה",
    "שלוש פעמים מדרגה שניה",
    "ארבע פעמים מדרגה שניה",
    "חמש פעמים מדרגה שניה",
    "שש פעמים מדרגה שניה",
    "שבע פעמים מדרגה שניה",
    "שמונה פעמים מדרגה שניה",
    "תשע פעמים מדרגה שניה",
    "עשר פעמים מדרגה שניה",
    "אחת עשרה פעמים מדרגה שניה",

]

_PARENTS_LEVEL = [

   "",
   "הורים",
   "סבים",
   "סבות",
   "סב גדול",
   "סבה גדולה",
   "סב וסבה גדולים",
   "סב וסבה מרמה שלישית",
   "סב וסבה מרמה רביעית",
   "סב וסבה מרמה חמישית",
   "סב וסבה מרמה שישית",
   "סב וסבה מרמה שביעית",
   "סב וסבה מרמה שמינית",
   "סב וסבה מרמה תשיעית",
   "סב וסבה מרמה עשירית",
   "סב וסבה מרמה אחת עשרה",

] _FATHER_LEVEL = [

   "",
   "סב %(step)s %(inlaw)s",
   "סב גדול %(step)s %(inlaw)s",
   "סב מרמה שלישית %(step)s %(inlaw)s",
   "סב מרמה רביעית %(step)s %(inlaw)s",
   "סב מרמה חמישית %(step)s %(inlaw)s",
   "סב מרמה שישית %(step)s %(inlaw)s",
   "סב מרמה שביעית %(step)s %(inlaw)s",
   "סב מרמה שמינית %(step)s %(inlaw)s",
   "סב מרמה תשיעית %(step)s %(inlaw)s",
   "סב מרמה עשירית %(step)s %(inlaw)s",
   "סב מרמה אחת עשרה %(step)s %(inlaw)s",

]

_MOTHER_LEVEL = [

   "",
   "סבה %(step)s %(inlaw)s",
   "סבה גדולה %(step)s %(inlaw)s",
   "סבה מרמה שלישית %(step)s %(inlaw)s",
   "סבה מרמה רביעית %(step)s %(inlaw)s",
   "סבה מרמה חמישית %(step)s %(inlaw)s",
   "סבה מרמה שישית %(step)s %(inlaw)s",
   "סבה מרמה שביעית %(step)s %(inlaw)s",
   "סבה מרמה שמינית %(step)s %(inlaw)s",
   "סבה מרמה תשיעית %(step)s %(inlaw)s",
   "סבה מרמה עשירית %(step)s %(inlaw)s",
   "סבה מרמה אחת עשרה %(step)s %(inlaw)s",

]

_SON_LEVEL = [

   "",
   "בן %(step)s %(inlaw)s",
   "נכד %(step)s %(inlaw)s",
   "נין %(step)s %(inlaw)s",
   "חימש %(step)s %(inlaw)s",
   "נכד מרמה חמישית %(step)s %(inlaw)s",
   "נכד מרמה שישית %(step)s %(inlaw)s",
   "נכד מרמה שביעית %(step)s %(inlaw)s",
   "נכד מרמה שמינית %(step)s %(inlaw)s",
   "נכד מרמה תשיעית %(step)s %(inlaw)s",
   "נכד מרמה עשירית %(step)s %(inlaw)s",
   "נכד מרמה אחת עשרה %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",
   "נכד רחוק %(step)s %(inlaw)s",


]

_DAUGHTER_LEVEL = [

   "",
   "בת %(step)s %(inlaw)s",
   "נכדה %(step)s %(inlaw)s",
   "נינה %(step)s %(inlaw)s",
   "חימשה %(step)s %(inlaw)s",
   "נכדה מרמה חמישית %(step)s %(inlaw)s",
   "נכדה מרמה שישית %(step)s %(inlaw)s",
   "נכדה מרמה שביעית %(step)s %(inlaw)s",
   "נכדה מרמה שמינית %(step)s %(inlaw)s",
   "נכדה מרמה תשיעית %(step)s %(inlaw)s",
   "נכדה מרמה עשירית %(step)s %(inlaw)s",
   "נכדה מרמה אחת עשרה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",
   "נכדה רחוקה %(step)s %(inlaw)s",

]

_SISTER_LEVEL = [

   "",
   "אחות %(step)s %(inlaw)s",
   "דודה %(step)s %(inlaw)s",
   "דודה גדולה %(step)s %(inlaw)s",
   "דודה מרמה שלישית %(step)s %(inlaw)s",
   "דודה מרמה רביעית %(step)s %(inlaw)s",
   "דודה מרמה חמישית %(step)s %(inlaw)s",
   "דודה מרמה שישית %(step)s %(inlaw)s",
   "דודה מרמה שביעית %(step)s %(inlaw)s",
   "דודה מרמה שמינית %(step)s %(inlaw)s",
   "דודה מרמה תשיעיתית %(step)s %(inlaw)s",
   "דודה מרמה עשירית %(step)s %(inlaw)s",
   "דודה מרמה אחת עשרה%(step)s %(inlaw)s",

]

_BROTHER_LEVEL = [

   "",
   "אח %(step)s %(inlaw)s",
   "דוד %(step)s %(inlaw)s",
   "דוד גדול %(step)s %(inlaw)s",
   "דוד מרמה שלישית %(step)s %(inlaw)s",
   "דוד מרמה רביעית %(step)s %(inlaw)s",
   "דוד מרמה חמישית %(step)s %(inlaw)s",
   "דוד מרמה שישית %(step)s %(inlaw)s",
   "דוד מרמה שביעית %(step)s %(inlaw)s",
   "דוד מרמה שמינית %(step)s %(inlaw)s",
   "דוד מרמה תשיעית %(step)s %(inlaw)s",
   "דוד מרמה עשירית %(step)s %(inlaw)s",
   "דוד מרמה אחת עשרה %(step)s %(inlaw)s",

]

_NEPHEW_LEVEL = [

   "",
   "אחיין %(step)s %(inlaw)s",
   "נכדן %(step)s %(inlaw)s",
   "אחיין גדול %(step)s %(inlaw)s",
   "אחיין מרמה שלישית %(step)s %(inlaw)s",
   "אחיין מרמה רביעית %(step)s %(inlaw)s",
   "אחיין מרמה חמישית %(step)s %(inlaw)s",
   "אחיין מרמה שישית %(step)s %(inlaw)s",
   "אחיין מרמה שביעית %(step)s %(inlaw)s",
   "אחיין מרמה שמינית %(step)s %(inlaw)s",
   "אחיין מרמה תשיעית %(step)s %(inlaw)s",
   "אחיין מרמה עשירית %(step)s %(inlaw)s",
   "אחיין מרמה אחת עשרה %(step)s %(inlaw)s",

]

_NIECE_LEVEL = [

   "",
   "אחיינית %(step)s %(inlaw)s",
   "נכדנית %(step)s %(inlaw)s",
   "אחיינית גדולה %(step)s %(inlaw)s",
   "אחיינית מרמה שלישית %(step)s %(inlaw)s",
   "אחיינית מרמה רביעית %(step)s %(inlaw)s",
   "אחיינית מרמה חמישית %(step)s %(inlaw)s",
   "אחיינית מרמה שישית %(step)s %(inlaw)s",
   "אחיינית מרמה שביעית %(step)s %(inlaw)s",
   "אחיינית מרמה שמינית %(step)s %(inlaw)s",
   "אחיינית מרמה תשיעית %(step)s %(inlaw)s",
   "אחיינית מרמה עשירית %(step)s %(inlaw)s",
   "אחיינית מרמה אחת עשרה %(step)s %(inlaw)s",

]

_CHILDREN_LEVEL = [

   "",
   "ילדים",
   "נכדים",
   "נינים",
   "חימשים",
   "נכדים מרמה חמישית",
   "נכדים מרמה שישית",
   "נכדים מרמה שביעית",
   "נכדים מרמה שמינית",
   "נכדים מרמה תשיעית",
   "נכדים מרמה עשירית",
   "נכדים מרמה אחת עשרה",

]

_SIBLINGS_LEVEL = [

   "",
   "אחאים",
   "דוד/דודה",
   "דוד/דודה גדולים",
   "דוד/דודה מרמה שלישית",
   "דוד/דודה מרמה רביעית",
   "דוד/דודה מרמה חמישית",
   "דוד/דודה מרמה שישית",
   "דוד/דודה מרמה שביעית",
   "דוד/דודה מרמה שמינית",
   "דוד/דודה מרמה תשיעית",
   "דוד/דודה מרמה עשירית",
   "דוד/דודה מרמה אחת עשרה",

]

_SIBLING_LEVEL = [

   "",
   "אחאים %(step)s %(inlaw)s",
   "דוד/דודה %(step)s %(inlaw)s",
   "דוד/דודה גדולים %(step)s %(inlaw)s",
   "דוד/דודה מרמה שלישית %(step)s %(inlaw)s",
   "דוד/דודה מרמה רביעית %(step)s %(inlaw)s",
   "דוד/דודה מרמה חמישית %(step)s %(inlaw)s",
   "דוד/דודה מרמה שישית %(step)s %(inlaw)s",
   "דוד/דודה מרמה שביעית %(step)s %(inlaw)s",
   "דוד/דודה מרמה שמינית %(step)s %(inlaw)s",
   "דוד/דודה מרמה תשיעית %(step)s %(inlaw)s",
   "דוד/דודה מרמה עשירית %(step)s %(inlaw)s",
   "דוד/דודה מרמה אחת עשרה %(step)s %(inlaw)s",

]

_NEPHEWS_NIECES_LEVEL = [

   "",
   "אחאים",
   "אחיין/אחיינית",
   "נכדן/נכדנית",
   "אחיין/אחיינית גדולים",
   "אחיין/אחיינית שלישית",
   "אחיין/אחיינית רביעית",
   "אחיין/אחיינית חמישית",
   "אחיין/אחיינית מרמה שישית",
   "אחיין/אחיינית מרמה שביעית",
   "אחיין/אחיינית מרמה שמינית",
   "אחיין/אחיינית מרמה תשיעית",
   "אחיין/אחיינית מרמה עשירית",
   "אחיין/אחיינית מרמה אחת עשרה",

]


  1. -------------------------------------------------------------------------
  2. RelationshipCalculator
  3. -------------------------------------------------------------------------

class RelationshipCalculator:

   """
   The relationship calculator helps to determine the relationship between
   two people.
   """
   REL_MOTHER = "m"  # going up to mother
   REL_FATHER = "f"  # going up to father
   REL_MOTHER_NOTBIRTH = "M"  # going up to mother, not birth relation
   REL_FATHER_NOTBIRTH = "F"  # going up to father, not birth relation
   REL_SIBLING = "s"  # going sideways to sibling (no parents)
   REL_FAM_BIRTH = "a"  # going up to family (mother and father)
   REL_FAM_NONBIRTH = "A"  # going up to family, not birth relation
   REL_FAM_BIRTH_MOTH_ONLY = "b"  # going up to fam, only birth rel to mother
   REL_FAM_BIRTH_FATH_ONLY = "c"  # going up to fam, only birth rel to father
   REL_FAM_INLAW_PREFIX = "L"  # going to the partner.
   # sibling types
   NORM_SIB = 0  # same birth parents
   HALF_SIB_MOTHER = 1  # same mother, father known to be different
   HALF_SIB_FATHER = 2  # same father, mother known to be different
   STEP_SIB = 3  # birth parents known to be different
   UNKNOWN_SIB = 4  # insufficient data to draw conclusion
   # sibling strings  for Hebrew we need four "step": male sing/plur, female sing/plur
   STEP = "שלוב"
   STEP_F = "שלובה"
   STEP_M = "שלוב"
   HALF = "למחצה"
   INLAW = "מחיתון"
   # partner types
   PARTNER_MARRIED = 1
   PARTNER_UNMARRIED = 2
   PARTNER_CIVIL_UNION = 3
   PARTNER_UNKNOWN_REL = 4
   PARTNER_EX_MARRIED = 5
   PARTNER_EX_UNMARRIED = 6
   PARTNER_EX_CIVIL_UNION = 7
   PARTNER_EX_UNKNOWN_REL = 8
   def __init__(self):
       self.signal_keys = []
       self.state_signal_key = None
       self.storemap = False
       self.dirtymap = True
       self.stored_map = None
       self.map_handle = None
       self.map_meta = None
       self.__db_connected = False
       self.depth = 15
       try:
           from .config import config
           self.set_depth(config.get("behavior.generation-depth"))
       except ImportError:
           pass
       # data storage to communicate with recursive functions
       self.__max_depth_reached = False
       self.__loop_detected = False
       self.__max_depth = 12
       self.__all_families = False
       self.__all_dist = False
       self.__only_birth = False
       self.__crosslinks = False
       self.__msg = []
   def set_depth(self, depth):
       """
       Set how deep relationships must be searched. Input must be an
       integer > 0
       """
       if depth != self.depth:
           self.depth = depth
           self.dirtymap = True
   def get_depth(self):
       """
       Obtain depth of relationship search
       """
       return self.depth
   DIST_FATHER = "אב־קדמון רחוק %(step)s %(inlaw)s (%(level)d דורות)"
   def _get_father(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level > len(_FATHER_LEVEL) - 1:
           return self.DIST_FATHER % {"step": step, "inlaw": inlaw, "level": level}
       else:
           return _FATHER_LEVEL[level] % {"step": step, "inlaw": inlaw}
   DIST_SON = "בן רחוק %(step) %(inlaw)s (%(level)d דורות)"
   def _get_son(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level > len(_SON_LEVEL) - 1:
           return self.DIST_SON % {"step": step, "inlaw": inlaw, "level": level}
       else:
           return _SON_LEVEL[level] % {"step": step, "inlaw": inlaw}
   DIST_MOTHER = "אם־קדמונית רחוקה %(step)s %(inlaw) s(%(level)d דורות)"
   def _get_mother(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level > len(_MOTHER_LEVEL) - 1:
           return self.DIST_MOTHER % {"step": step, "inlaw": inlaw, "level": level}
       else:
           return _MOTHER_LEVEL[level] % {"step": step, "inlaw": inlaw}
   DIST_DAUGHTER = "בת רחוקה %(step) %(inlaw)s(%(level)d דורות)"
   def _get_daughter(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level > len(_DAUGHTER_LEVEL) - 1:
           return self.DIST_DAUGHTER % {"step": step, "inlaw": inlaw, "level": level}
       else:
           return _DAUGHTER_LEVEL[level] % {"step": step, "inlaw": inlaw}
   def _get_parent_unknown(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level < len(_LEVEL_NAME):
           return ("אב־קדמון %(step)s %(inlaw)s" % {"step": step, "inlaw": inlaw} + _LEVEL_NAME[level])
       else:
            return "אב־קדמון רחוק %s %s (%d דורות)" % (step, inlaw, level)
   DIST_CHILD = "צאצא רחוק %(step)s (%(level)d דורות)"
   def _get_child_unknown(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level < len(_LEVEL_NAME):
           return ("צאצא %(step)s %(inlaw)s" % {"step": step, "inlaw": inlaw} + _LEVEL_NAME[level])
       else:
           return self.DIST_CHILD % {"step": step, "level": level}
   DIST_AUNT = "דודה רחוקה %(step)s %(inlaw)s"
   def _get_aunt(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level > len(_SISTER_LEVEL) - 1:
           return self.DIST_AUNT % {"step": step, "inlaw": inlaw}
       else:
           return _SISTER_LEVEL[level] % {"step": step, "inlaw": inlaw}
   DIST_UNCLE = "דוד רחוק %(step)s %(inlaw)s"
   def _get_uncle(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level > len(_BROTHER_LEVEL) - 1:
           return self.DIST_UNCLE % {"step": step, "inlaw": inlaw}
       else:
           return _BROTHER_LEVEL[level] % {"step": step, "inlaw": inlaw}
   DIST_NEPHEW = "אחיין רחוק %(step)s %(inlaw)s"
   def _get_nephew(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level > len(_NEPHEW_LEVEL) - 1:
           return self.DIST_NEPHEW % {"step": step, "inlaw": inlaw}
       else:
           return _NEPHEW_LEVEL[level] % {"step": step, "inlaw": inlaw}
   DIST_NIECE = "אחיינית רחוקה %(step)s %(inlaw)s"
   def _get_niece(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level > len(_NIECE_LEVEL) - 1:
           return self.DIST_NIECE % {"step": step, "inlaw": inlaw}
       else:
           return _NIECE_LEVEL[level] % {"step": step, "inlaw": inlaw}
   def _get_cousin(self, level, removed, dir="", step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if removed == 0 and level < len(_LEVEL_NAME):
           return "בן־דוד %s %s %s" % (step, inlaw, _LEVEL_NAME[level])
       elif removed > len(_REMOVED_LEVEL) - 1 or level > len(_LEVEL_NAME) - 1:
           return "קרוב־משפחה רחוק %s %s" % (step, inlaw)
       else:
           return "בן־דוד/בת־דודה %s %s %s %s %s" % (step, inlaw, _LEVEL_NAME[level], _REMOVED_LEVEL[removed], dir,)
   DIST_SIB = "דוד/דודה רחוקים %(step)s %(inlaw)s"
   def _get_sibling(self, level, step="", inlaw=""):
       """
       Internal english method to create relation string
       """
       if level < len(_SIBLING_LEVEL):
           return _SIBLING_LEVEL[level] % {"step": step, "inlaw": inlaw}
       else:
           return self.DIST_SIB % {"step": step, "inlaw": inlaw}
   def get_sibling_type(self, db, orig, other):
       """
       Translation free determination of type of orig and other as siblings
       The procedure returns sibling types, these can be passed to
       get_sibling_relationship_string.
       Only call this method if known that orig and other are siblings
       """
       fatherorig, motherorig = self.get_birth_parents(db, orig)
       fatherother, motherother = self.get_birth_parents(db, other)
       if fatherorig and motherorig and fatherother and motherother:
           if fatherother == fatherorig and motherother == motherorig:
               return self.NORM_SIB
           elif fatherother == fatherorig:
               # all birth parents are known, one
               return self.HALF_SIB_FATHER
           elif motherother == motherorig:
               return self.HALF_SIB_MOTHER
           else:
               return self.STEP_SIB
       else:
           # some birth parents are not known, hence we or cannot know if
           # half siblings. step siblings might be possible, otherwise give up
           orig_nb_par = self._get_nonbirth_parent_list(db, orig)
           if fatherother and fatherother in orig_nb_par:
               # the birth parent of other is non-birth of orig
               if motherother and motherother == motherorig:
                   return self.HALF_SIB_MOTHER
               else:
                   return self.STEP_SIB
           if motherother and motherother in orig_nb_par:
               # the birth parent of other is non-birth of orig
               if fatherother and fatherother == fatherorig:
                   return self.HALF_SIB_FATHER
               else:
                   return self.STEP_SIB
           other_nb_par = self._get_nonbirth_parent_list(db, other)
           if fatherorig and fatherorig in other_nb_par:
               # the one birth parent of other is non-birth of orig
               if motherorig and motherother == motherorig:
                   return self.HALF_SIB_MOTHER
               else:
                   return self.STEP_SIB
           if motherorig and motherorig in other_nb_par:
               # the one birth parent of other is non-birth of orig
               if fatherother and fatherother == fatherorig:
                   return self.HALF_SIB_FATHER
               else:
                   return self.STEP_SIB
           # there is an unknown birth parent, it could be that this is the
           # birth parent of the other person
           return self.UNKNOWN_SIB
   def get_birth_parents(self, db, person):
       """
       Method that returns the birthparents of a person as tuple
       (mother handle, father handle), if no known birthparent, the
       handle is replaced by None
       """
       birthfather = None
       birthmother = None
       for fam in person.get_parent_family_handle_list():
           family = db.get_family_from_handle(fam)
           if not family:
               continue
           childrel = [
               (ref.get_mother_relation(), ref.get_father_relation())
               for ref in family.get_child_ref_list()
               if ref.ref == person.handle
           ]
           if not birthmother and childrel[0][0] == ChildRefType.BIRTH:
               birthmother = family.get_mother_handle()
           if not birthfather and childrel[0][1] == ChildRefType.BIRTH:
               birthfather = family.get_father_handle()
           if birthmother and birthfather:
               break
       return (birthmother, birthfather)
   def _get_nonbirth_parent_list(self, db, person):
       """
       Returns a list of handles of parents of which it is known
       they are not birth parents.
       So all parents which do not have relation BIRTH or UNKNOWN
       are returned.
       """
       nb_parents = []
       for fam in person.get_parent_family_handle_list():
           family = db.get_family_from_handle(fam)
           if not family:
               continue
           childrel = [
               (ref.get_mother_relation(), ref.get_father_relation())
               for ref in family.get_child_ref_list()
               if ref.ref == person.handle
           ]
           if (
               childrel[0][0] != ChildRefType.BIRTH
               and childrel[0][0] != ChildRefType.UNKNOWN
           ):
               nb_parents.append(family.get_mother_handle())
           if (
               childrel[0][1] != ChildRefType.BIRTH
               and childrel[0][1] != ChildRefType.UNKNOWN
           ):
               nb_parents.append(family.get_father_handle())
       # make every person appear only once:
       return list(set(nb_parents))
   def _get_spouse_type(self, db, orig, other, all_rel=False):
       """
       Translation free determination if orig and other are partners.
       The procedure returns partner types, these can be passed to
       get_partner_relationship_string.
       If all_rel=False, returns None or a partner type.
       If all_rel=True, returns a list, empty if no partner
       """
       val = []
       for family_handle in orig.get_family_handle_list():
           family = db.get_family_from_handle(family_handle)
           # return first found spouse type
           if family and other.get_handle() in [
               family.get_father_handle(),
               family.get_mother_handle(),
           ]:
               family_rel = family.get_relationship()
               # check for divorce event:
               ex = False
               for eventref in family.get_event_ref_list():
                   event = db.get_event_from_handle(eventref.ref)
                   if event and (
                       event.get_type() == EventType.DIVORCE
                       or event.get_type() == EventType.ANNULMENT
                   ):
                       ex = True
                       break
               if family_rel == FamilyRelType.MARRIED:
                   if ex:
                       val.append(self.PARTNER_EX_MARRIED)
                   else:
                       val.append(self.PARTNER_MARRIED)
               elif family_rel == FamilyRelType.UNMARRIED:
                   if ex:
                       val.append(self.PARTNER_EX_UNMARRIED)
                   else:
                       val.append(self.PARTNER_UNMARRIED)
               elif family_rel == FamilyRelType.CIVIL_UNION:
                   if ex:
                       val.append(self.PARTNER_EX_CIVIL_UNION)
                   else:
                       val.append(self.PARTNER_CIVIL_UNION)
               else:
                   if ex:
                       val.append(self.PARTNER_EX_UNKNOWN_REL)
                   else:
                       val.append(self.PARTNER_UNKNOWN_REL)
       if all_rel:
           return val
       else:
           # last relation is normally the defenitive relation
           if val:
               return val[-1]
           else:
               return None
   def is_spouse(self, db, orig, other, all_rel=False):
       """
       Determine the spouse relation
       """
       spouse_type = self._get_spouse_type(db, orig, other, all_rel)
       if spouse_type:
           return self.get_partner_relationship_string(
               spouse_type, orig.get_gender(), other.get_gender()
           )
       else:
           return None
   def get_relationship_distance_new(
       self,
       db,
       orig_person,
       other_person,
       all_families=False,
       all_dist=False,
       only_birth=True,
   ):
       """
       Return if all_dist == True a 'tuple, string':
       (rank, person handle, firstRel_str, firstRel_fam,
       secondRel_str, secondRel_fam), msg
       or if all_dist == True a 'list of tuple, string':
       [.....], msg:
       .. note:: _new can be removed once all rel_xx modules no longer
                 overwrite get_relationship_distance
       The tuple or list of tuples consists of:
       ==============  =====================================================
       Element         Description
       ==============  =====================================================
       rank            Total number of generations from common ancestor to
                       the two persons, rank is -1 if no relations found
       person_handle   The Common ancestor
       firstRel_str    String with the path to the common ancestor
                       from orig Person
       firstRel_fam    Family numbers along the path as a list, eg [0,0,1].
                       For parent in multiple families, eg [0. [0, 2], 1]
       secondRel_str   String with the path to the common ancestor
                       from otherPerson
       secondRel_fam   Family numbers along the path, eg [0,0,1].
                       For parent in multiple families, eg [0. [0, 2], 1]
       msg             List of messages indicating errors. Empyt list if no
                       errors.
       ==============  =====================================================
       Example:  firstRel_str = 'ffm' and firstRel_fam = [2,0,1] means
       common ancestor is mother of the second family of the father of the
       first family of the father of the third family.
       Note that the same person might be present twice if the person is
       reached via a different branch too. Path (firstRel_str and
       secondRel_str) will of course be different.
       :param db: database to work on
       :param orig_person: first person
       :type orig_person: Person Obj
       :param other_person: second person, relation is sought between
                            first and second person
       :type other_person:  Person Obj
       :param all_families: if False only Main family is searched, otherwise
                            all families are used
       :type all_families: bool
       :param all_dist: if False only the shortest distance is returned,
                        otherwise all relationships
       :type all_dist:  bool
       :param only_birth: if True only parents with birth relation are
                          considered
       :type only_birth:  bool
       """
       # data storage to communicate with recursive functions
       self.__max_depth_reached = False
       self.__loop_detected = False
       self.__max_depth = self.get_depth()
       self.__all_families = all_families
       self.__all_dist = all_dist
       self.__only_birth = only_birth
       self.__crosslinks = False  # no crosslinks
       first_rel = -1
       second_rel = -1
       self.__msg = []
       common = []
       first_map = {}
       second_map = {}
       rank = 9999999
       try:
           if (
               self.storemap
               and self.stored_map is not None
               and self.map_handle == orig_person.handle
               and not self.dirtymap
           ):
               first_map = self.stored_map
               (
                   self.__max_depth_reached,
                   self.__loop_detected,
                   self.__all_families,
                   self.__all_dist,
                   self.__only_birth,
                   self.__crosslinks,
                   self.__msg,
               ) = self.map_meta
               self.__msg = list(self.__msg)
           else:
               self.__apply_filter(db, orig_person, "", [], first_map)
               self.map_meta = (
                   self.__max_depth_reached,
                   self.__loop_detected,
                   self.__all_families,
                   self.__all_dist,
                   self.__only_birth,
                   self.__crosslinks,
                   list(self.__msg),
               )
           self.__apply_filter(
               db, other_person, "", [], second_map, stoprecursemap=first_map
           )
       except RuntimeError:
           return (-1, None, -1, [], -1, []), [
               _(
                   "מספר הדורות באילן היוחסין גבוהה ממספר הדורות המירבי "
                   "נסרקו %d דורות .\nלא מן הנמנע "
                   "שהתפספסו קשרי קירבת משפחה."
               )
           ] + self.__msg
       if self.storemap:
           self.stored_map = first_map
           self.dirtymap = False
           self.map_handle = orig_person.handle
       for person_handle in second_map:
           if person_handle in first_map:
               com = []
               # a common ancestor
               for rel1, fam1 in zip(
                   first_map[person_handle][0], first_map[person_handle][1]
               ):
                   len1 = len(rel1)
                   for rel2, fam2 in zip(
                       second_map[person_handle][0], second_map[person_handle][1]
                   ):
                       len2 = len(rel2)
                       # collect paths to arrive at common ancestor
                       com.append((len1 + len2, person_handle, rel1, fam1, rel2, fam2))
               # insert common ancestor in correct position,
               #  if shorter links, check if not subset
               #  if longer links, check if not superset
               pos = 0
               for ranknew, handlenew, rel1new, fam1new, rel2new, fam2new in com:
                   insert = True
                   for rank, handle, rel1, fam1, rel2, fam2 in common:
                       if ranknew < rank:
                           break
                       elif ranknew >= rank:
                           # check subset
                           if (
                               rel1 == rel1new[: len(rel1)]
                               and rel2 == rel2new[: len(rel2)]
                           ):
                               # subset relation exists already
                               insert = False
                               break
                       pos += 1
                   if insert:
                       if common:
                           common.insert(
                               pos,
                               (
                                   ranknew,
                                   handlenew,
                                   rel1new,
                                   fam1new,
                                   rel2new,
                                   fam2new,
                               ),
                           )
                       else:
                           common = [
                               (ranknew, handlenew, rel1new, fam1new, rel2new, fam2new)
                           ]
                       # now check if superset must be deleted from common
                       deletelist = []
                       index = pos + 1
                       for rank, handle, rel1, fam1, rel2, fam2 in common[pos + 1 :]:
                           if (
                               rel1new == rel1[: len(rel1new)]
                               and rel2new == rel2[: len(rel2new)]
                           ):
                               deletelist.append(index)
                           index += 1
                       deletelist.reverse()
                       for index in deletelist:
                           del common[index]
       # check for extra messages
       if self.__max_depth_reached:
           self.__msg += [
               _(
                   "מספר הדורות באילן היוחסין גבוהה ממספר הדורות המירבי "
                   "נסרקו %d דורות .\nלא מן הנמנע "
                   "שהתפספסו קשרי קירבת משפחה."
               )
               % (self.__max_depth)
           ]
       if common and not self.__all_dist:
           rank = common[0][0]
           person_handle = common[0][1]
           first_rel = common[0][2]
           first_fam = common[0][3]
           second_rel = common[0][4]
           second_fam = common[0][5]
           return (
               rank,
               person_handle,
               first_rel,
               first_fam,
               second_rel,
               second_fam,
           ), self.__msg
       if common:
           # list with tuples (rank, handle person,rel_str_orig,rel_fam_orig,
           #       rel_str_other,rel_fam_str) and messages
           return common, self.__msg
       if not self.__all_dist:
           return (-1, None, "", [], "", []), self.__msg
       else:
           return [(-1, None, "", [], "", [])], self.__msg
   def __apply_filter(
       self, db, person, rel_str, rel_fam, pmap, depth=1, stoprecursemap=None
   ):
       """
       Typically this method is called recursively in two ways:
       First method is stoprecursemap= None
       In this case a recursemap is builded by storing all data.
       Second method is with a stoprecursemap given
       In this case parents are recursively looked up. If present in
       stoprecursemap, a common ancestor is found, and the method can
       stop looking further. If however self.__crosslinks == True, the data
       of first contains loops, and parents
       will be looked up anyway an stored if common. At end the doubles
       are filtered out
       """
       if person is None or not person.handle:
           return
       if depth > self.__max_depth:
           self.__max_depth_reached = True
           # print('Maximum ancestor generations ('+str(depth)+') reached', \
           #            '(' + rel_str + ').',\
           #            'Stopping relation algorithm.')
           return
       depth += 1
       commonancestor = False
       store = True  # normally we store all parents
       if stoprecursemap:
           store = False  # but not if a stop map given
           if person.handle in stoprecursemap:
               commonancestor = True
               store = True
       # add person to the map, take into account that person can be obtained
       # from different sides
       if person.handle in pmap:
           # person is already a grandparent in another branch, we already have
           # had lookup of all parents, we call that a crosslink
           if not stoprecursemap:
               self.__crosslinks = True
           pmap[person.handle][0] += [rel_str]
           pmap[person.handle][1] += [rel_fam]
           # check if there is no loop father son of his son, ...
           # loop means person is twice reached, same rel_str in begin
           for rel1 in pmap[person.handle][0]:
               for rel2 in pmap[person.handle][0]:
                   if len(rel1) < len(rel2) and rel1 == rel2[: len(rel1)]:
                       # loop, keep one message in storage!
                       self.__loop_detected = True
                       self.__msg += [
                           _("Relationship loop detected:")
                           + " "
                           + _(
                               "Person %(person)s connects to himself via %(relation)s"
                           )
                           % {
                               "person": person.get_primary_name().get_name(),
                               "relation": rel2[len(rel1) :],
                           }
                       ]
                       return
       elif store:
           pmap[person.handle] = [[rel_str], [rel_fam]]
       # having added person to the pmap, we only look up recursively to
       # parents if this person is not common relative
       # if however the first map has crosslinks, we need to continue reduced
       if commonancestor and not self.__crosslinks:
           # don't continue search, great speedup!
           return
       family_handles = []
       main = person.get_main_parents_family_handle()
       if main:
           family_handles = [main]
       if self.__all_families:
           family_handles = person.get_parent_family_handle_list()
       try:
           parentstodo = {}
           fam = 0
           for family_handle in family_handles:
               rel_fam_new = rel_fam + [fam]
               family = db.get_family_from_handle(family_handle)
               if not family:
                   continue
               # obtain childref for this person
               childrel = [
                   (ref.get_mother_relation(), ref.get_father_relation())
                   for ref in family.get_child_ref_list()
                   if ref.ref == person.handle
               ]
               fhandle = family.father_handle
               mhandle = family.mother_handle
               for data in [
                   (
                       fhandle,
                       self.REL_FATHER,
                       self.REL_FATHER_NOTBIRTH,
                       childrel[0][1],
                   ),
                   (
                       mhandle,
                       self.REL_MOTHER,
                       self.REL_MOTHER_NOTBIRTH,
                       childrel[0][0],
                   ),
               ]:
                   if data[0] and data[0] not in parentstodo:
                       persontodo = db.get_person_from_handle(data[0])
                       if data[3] == ChildRefType.BIRTH:
                           addstr = data[1]
                       elif not self.__only_birth:
                           addstr = data[2]
                       else:
                           addstr = ""
                       if addstr:
                           parentstodo[data[0]] = (
                               persontodo,
                               rel_str + addstr,
                               rel_fam_new,
                           )
                   elif data[0] and data[0] in parentstodo:
                       # this person is already scheduled to research
                       # update family list
                       famlist = parentstodo[data[0]][2]
                       if not isinstance(famlist[-1], list) and fam != famlist[-1]:
                           famlist = famlist[:-1] + [[famlist[-1]]]
                       if isinstance(famlist[-1], list) and fam not in famlist[-1]:
                           famlist = famlist[:-1] + [famlist[-1] + [fam]]
                           parentstodo[data[0]] = (
                               parentstodo[data[0]][0],
                               parentstodo[data[0]][1],
                               famlist,
                           )
               if not fhandle and not mhandle and stoprecursemap is None:
                   # family without parents, add brothers for orig person
                   # other person has recusemap, and will stop when seeing
                   # the brother.
                   child_list = [
                       ref.ref
                       for ref in family.get_child_ref_list()
                       if ref.ref != person.handle
                   ]
                   addstr = self.REL_SIBLING
                   for chandle in child_list:
                       if chandle in pmap:
                           pmap[chandle][0] += [rel_str + addstr]
                           pmap[chandle][1] += [rel_fam_new]
                           # person is already a grandparent in another branch
                       else:
                           pmap[chandle] = [[rel_str + addstr], [rel_fam_new]]
               fam += 1
           for handle, data in parentstodo.items():
               self.__apply_filter(
                   db, data[0], data[1], data[2], pmap, depth, stoprecursemap
               )
       except:
           import traceback
           traceback.print_exc()
           return
   def collapse_relations(self, relations):
       """
       Internal method to condense the relationships as returned by
       get_relationship_distance_new.
       Common ancestors in the same family are collapsed to one entry,
       changing the person paths to family paths, eg 'mf' and 'mm' become 'ma'
       relations : list of relations as returned by
                   get_relationship_distance_new with all_dist = True
       returns : the same data as relations, but collapsed, hence the
                 handle entry is now a list of handles, and the
                 path to common ancestors can now contain family
                 identifiers (eg 'a', ...)
                 In the case of sibling, this is replaced by family
                 with common ancestor handles empty list []!
       """
       if relations[0][0] == -1:
           return relations
       commonnew = []
       existing_path = []
       for relation in relations:
           relstrfirst = None
           commonhandle = [relation[1]]
           if relation[2]:
               relstrfirst = relation[2][:-1]
           relstrsec = None
           if relation[4]:
               relstrsec = relation[4][:-1]
           relfamfirst = relation[3][:]
           relfamsec = relation[5][:]
           # handle pure sibling:
           rela2 = relation[2]
           rela4 = relation[4]
           if relation[2] and relation[2][-1] == self.REL_SIBLING:
               # sibling will be the unique common ancestor,
               # change to a family with unknown handle for common ancestor
               rela2 = relation[2][:-1] + self.REL_FAM_BIRTH
               rela4 = relation[4] + self.REL_FAM_BIRTH
               relfamsec = relfamsec + [relfamfirst[-1]]
               relstrsec = relation[4][:-1]
               commonhandle = []
           # a unique path to family of common person:
           familypaths = []
           if relfamfirst and isinstance(relfamfirst[-1], list):
               if relfamsec and isinstance(relfamsec[-1], list):
                   for val1 in relfamfirst[-1]:
                       for val2 in relfamsec[-1]:
                           familypaths.append(
                               (
                                   relstrfirst,
                                   relstrsec,
                                   relfamfirst[:-1] + [val1],
                                   relfamsec[:-1] + [val2],
                               )
                           )
               else:
                   for val1 in relfamfirst[-1]:
                       familypaths.append(
                           (
                               relstrfirst,
                               relstrsec,
                               relfamfirst[:-1] + [val1],
                               relfamsec,
                           )
                       )
           elif relfamsec and isinstance(relfamsec[-1], list):
               for val2 in relfamsec[-1]:
                   familypaths.append(
                       (relstrfirst, relstrsec, relfamfirst, relfamsec[:-1] + [val2])
                   )
           else:
               familypaths.append((relstrfirst, relstrsec, relfamfirst, relfamsec))
           for familypath in familypaths:
               # familypath = (relstrfirst, relstrsec, relfamfirst, relfamsec)
               try:
                   posfam = existing_path.index(familypath)
               except ValueError:
                   posfam = None
               # if relstr is , the ancestor is unique, if posfam None,
               # first time we see this family path
               if (
                   posfam is not None
                   and relstrfirst is not None
                   and relstrsec is not None
               ):
                   # We already have a common ancestor of this family, just
                   # add the other, setting correct family relation.
                   tmp = commonnew[posfam]
                   frstcomstr = rela2[-1]
                   scndcomstr = tmp[2][-1]
                   newcomstra = self._famrel_from_persrel(frstcomstr, scndcomstr)
                   frstcomstr = rela4[-1]
                   scndcomstr = tmp[4][-1]
                   newcomstrb = self._famrel_from_persrel(frstcomstr, scndcomstr)
                   commonnew[posfam] = (
                       tmp[0],
                       tmp[1] + commonhandle,
                       rela2[:-1] + newcomstra,
                       tmp[3],
                       rela4[:-1] + newcomstrb,
                       tmp[5],
                   )
               else:
                   existing_path.append(familypath)
                   commonnew.append(
                       (
                           relation[0],
                           commonhandle,
                           rela2,
                           familypath[2],
                           rela4,
                           familypath[3],
                       )
                   )
       # we now have multiple person handles, single families, now collapse
       #  families again if all else equal
       collapsed = commonnew[:1]
       for rel in commonnew[1:]:
           found = False
           for newrel in collapsed:
               if newrel[0:3] == rel[0:3] and newrel[4] == rel[4]:
                   # another familypath to arrive at same result, merge
                   path1 = []
                   path2 = []
                   for a, b in zip(newrel[3], rel[3]):
                       if a == b:
                           path1.append(a)
                       elif isinstance(a, list):
                           path1.append(a.append(b))
                       else:
                           path1.append([a, b])
                   for a, b in zip(newrel[5], rel[5]):
                       if a == b:
                           path2.append(a)
                       elif isinstance(a, list):
                           path2.append(a.append(b))
                       else:
                           path2.append([a, b])
                   newrel[3][:] = path1[:]
                   newrel[5][:] = path2[:]
                   found = True
                   break
           if not found:
               collapsed.append(rel)
       return collapsed
   def _famrel_from_persrel(self, persrela, persrelb):
       """
       Conversion from eg 'f' and 'm' to 'a', so relation to the two
       persons of a common family is converted to a family relation
       """
       if persrela == persrelb:
           # should not happen, procedure called in error, just return value
           return persrela
       if (persrela == self.REL_MOTHER and persrelb == self.REL_FATHER) or (
           persrelb == self.REL_MOTHER and persrela == self.REL_FATHER
       ):
           return self.REL_FAM_BIRTH
       if (persrela == self.REL_MOTHER and persrelb == self.REL_FATHER_NOTBIRTH) or (
           persrelb == self.REL_MOTHER and persrela == self.REL_FATHER_NOTBIRTH
       ):
           return self.REL_FAM_BIRTH_MOTH_ONLY
       if (persrela == self.REL_FATHER and persrelb == self.REL_MOTHER_NOTBIRTH) or (
           persrelb == self.REL_FATHER and persrela == self.REL_MOTHER_NOTBIRTH
       ):
           return self.REL_FAM_BIRTH_FATH_ONLY
       # catch calling with family relations already, return val
       if (
           persrela == self.REL_FAM_BIRTH
           or persrela == self.REL_FAM_BIRTH_FATH_ONLY
           or persrela == self.REL_FAM_BIRTH_MOTH_ONLY
           or persrela == self.REL_FAM_NONBIRTH
       ):
           return persrela
       if (
           persrelb == self.REL_FAM_BIRTH
           or persrelb == self.REL_FAM_BIRTH_FATH_ONLY
           or persrelb == self.REL_FAM_BIRTH_MOTH_ONLY
           or persrelb == self.REL_FAM_NONBIRTH
       ):
           return persrelb
       return self.REL_FAM_NONBIRTH
   def only_birth(self, path):
       """
       Given a path to common ancestor. Return True if only birth
       relations, False otherwise
       """
       for value in path:
           if value in [
               self.REL_FAM_NONBIRTH,
               self.REL_FATHER_NOTBIRTH,
               self.REL_MOTHER_NOTBIRTH,
           ]:
               return False
       return True
   def get_one_relationship(
       self, db, orig_person, other_person, extra_info=False, olocale=glocale
   ):
       """
       Returns a string representing the most relevant relationship between
       the two people. If extra_info = True, extra information is returned:
       (relation_string, distance_common_orig, distance_common_other)
       If olocale is passed in (a GrampsLocale) that language will be used.
       :param olocale: allow selection of the relationship language
       :type olocale: a GrampsLocale instance
       """
       self._locale = olocale
       stop = False
       if orig_person is None:
           rel_str = _("undefined")
           stop = True
       if not stop and orig_person.get_handle() == other_person.get_handle():
           rel_str = ""
           stop = True
       if not stop:
           is_spouse = self.is_spouse(db, orig_person, other_person)
           if is_spouse:
               rel_str = is_spouse
               stop = True
       if stop:
           if extra_info:
               return (rel_str, -1, -1)
           else:
               return rel_str
       data, msg = self.get_relationship_distance_new(
           db,
           orig_person,
           other_person,
           all_dist=True,
           all_families=True,
           only_birth=False,
       )
       if data[0][0] == -1:
           if extra_info:
               return ("", -1, -1)
           else:
               return ""
       data = self.collapse_relations(data)
       # most relevant relationship is a birth family relation of lowest rank
       databest = [data[0]]
       rankbest = data[0][0]
       for rel in data:
           # data is sorted on rank
           if rel[0] == rankbest:
               databest.append(rel)
       rel = databest[0]
       dist_orig = len(rel[2])
       dist_other = len(rel[4])
       if len(databest) == 1:
           birth = self.only_birth(rel[2]) and self.only_birth(rel[4])
           if dist_orig == dist_other == 1:
               rel_str = self.get_sibling_relationship_string(
                   self.get_sibling_type(db, orig_person, other_person),
                   orig_person.get_gender(),
                   other_person.get_gender(),
               )
           else:
               rel_str = self.get_single_relationship_string(
                   dist_orig,
                   dist_other,
                   orig_person.get_gender(),
                   other_person.get_gender(),
                   rel[2],
                   rel[4],
                   only_birth=birth,
                   in_law_a=False,
                   in_law_b=False,
               )
       else:
           order = [
               self.REL_FAM_BIRTH,
               self.REL_FAM_BIRTH_MOTH_ONLY,
               self.REL_FAM_BIRTH_FATH_ONLY,
               self.REL_MOTHER,
               self.REL_FATHER,
               self.REL_SIBLING,
               self.REL_FAM_NONBIRTH,
               self.REL_MOTHER_NOTBIRTH,
               self.REL_FATHER_NOTBIRTH,
           ]
           orderbest = order.index(self.REL_MOTHER)
           for relother in databest:
               relbirth = self.only_birth(rel[2]) and self.only_birth(rel[4])
               if relother[2] == "" or relother[4] == "":
                   # direct relation, take that
                   rel = relother
                   break
               if (
                   not relbirth
                   and self.only_birth(relother[2])
                   and self.only_birth(relother[4])
               ):
                   # birth takes precedence
                   rel = relother
                   continue
               if (
                   order.index(relother[2][-1]) < order.index(rel[2][-1])
                   and order.index(relother[2][-1]) < orderbest
               ):
                   rel = relother
                   continue
               if (
                   order.index(relother[4][-1]) < order.index(rel[4][-1])
                   and order.index(relother[4][-1]) < orderbest
               ):
                   rel = relother
                   continue
               if (
                   order.index(rel[2][-1]) < orderbest
                   or order.index(rel[4][-1]) < orderbest
               ):
                   # keep the good one
                   continue
               if order.index(relother[2][-1]) < order.index(rel[2][-1]):
                   rel = relother
                   continue
               if order.index(relother[2][-1]) == order.index(
                   rel[2][-1]
               ) and order.index(relother[4][-1]) < order.index(rel[4][-1]):
                   rel = relother
                   continue
           dist_orig = len(rel[2])
           dist_other = len(rel[4])
           birth = self.only_birth(rel[2]) and self.only_birth(rel[4])
           if dist_orig == dist_other == 1:
               rel_str = self.get_sibling_relationship_string(
                   self.get_sibling_type(db, orig_person, other_person),
                   orig_person.get_gender(),
                   other_person.get_gender(),
               )
           else:
               rel_str = self.get_single_relationship_string(
                   dist_orig,
                   dist_other,
                   orig_person.get_gender(),
                   other_person.get_gender(),
                   rel[2],
                   rel[4],
                   only_birth=birth,
                   in_law_a=False,
                   in_law_b=False,
               )
       if extra_info:
           return (rel_str, dist_orig, dist_other)
       else:
           return rel_str
   def get_all_relationships(self, db, orig_person, other_person):
       """
       Return a tuple, of which the first entry is a list with all
       relationships in text, and the second a list of lists of all common
       ancestors that have that text as relationship
       """
       relstrings = []
       commons = {}
       if orig_person is None:
           return ([], [])
       if orig_person.get_handle() == other_person.get_handle():
           return ([], [])
       is_spouse = self.is_spouse(db, orig_person, other_person)
       if is_spouse:
           relstrings.append(is_spouse)
           commons[is_spouse] = []
       data, msg = self.get_relationship_distance_new(
           db,
           orig_person,
           other_person,
           all_dist=True,
           all_families=True,
           only_birth=False,
       )
       if data[0][0] != -1:
           data = self.collapse_relations(data)
           for rel in data:
               rel2 = rel[2]
               rel4 = rel[4]
               rel1 = rel[1]
               dist_orig = len(rel[2])
               dist_other = len(rel[4])
               if rel[2] and rel[2][-1] == self.REL_SIBLING:
                   rel2 = rel2[:-1] + self.REL_FAM_BIRTH
                   dist_other += 1
                   rel4 = rel4 + self.REL_FAM_BIRTH
                   rel1 = None
               birth = self.only_birth(rel2) and self.only_birth(rel4)
               if dist_orig == dist_other == 1:
                   rel_str = self.get_sibling_relationship_string(
                       self.get_sibling_type(db, orig_person, other_person),
                       orig_person.get_gender(),
                       other_person.get_gender(),
                   )
               else:
                   rel_str = self.get_single_relationship_string(
                       dist_orig,
                       dist_other,
                       orig_person.get_gender(),
                       other_person.get_gender(),
                       rel2,
                       rel4,
                       only_birth=birth,
                       in_law_a=False,
                       in_law_b=False,
                   )
               if rel_str not in relstrings:
                   relstrings.append(rel_str)
                   if rel1:
                       commons[rel_str] = rel1
                   else:
                       # unknown parent eg
                       commons[rel_str] = []
               else:
                   if rel1:
                       commons[rel_str].extend(rel1)
       # construct the return tupply, relstrings is ordered on rank automatic
       common_list = []
       for rel_str in relstrings:
           common_list.append(commons[rel_str])
       return (relstrings, common_list)
   def get_plural_relationship_string(
       self,
       Ga,
       Gb,
       reltocommon_a="",
       reltocommon_b="",
       only_birth=True,
       in_law_a=False,
       in_law_b=False,
   ):
       """
       Provide a string that describes the relationsip between a person, and
       a group of people with the same relationship. E.g. "grandparents" or
       "children".
       Ga and Gb can be used to mathematically calculate the relationship.
       .. seealso::
           http://en.wikipedia.org/wiki/Cousin#Mathematical_definitions
       :param Ga: The number of generations between the main person and the
                  common ancestor.
       :type Ga: int
       :param Gb: The number of generations between the group of people and the
                  common ancestor
       :type Gb: int
       :param reltocommon_a: relation path to common ancestor or common
                             Family for person a.
                             Note that length = Ga
       :type reltocommon_a: str
       :param reltocommon_b: relation path to common ancestor or common
                             Family for person b.
                             Note that length = Gb
       :type reltocommon_b: str
       :param only_birth: True if relation between a and b is by birth only
                          False otherwise
       :type only_birth: bool
       :param in_law_a: True if path to common ancestors is via the partner
                        of person a
       :type in_law_a: bool
       :param in_law_b: True if path to common ancestors is via the partner
                        of person b
       :type in_law_b: bool
       :returns: A string describing the relationship between the person and
                 the group.
       :rtype: str
       """
       rel_str = "קרובי־משפחה רחוקים"
       if Ga == 0:
           # These are descendants
           if Gb < len(_CHILDREN_LEVEL):
               rel_str = _CHILDREN_LEVEL[Gb]
           else:
               rel_str = "צאצאים רחוקים"
       elif Gb == 0:
           # These are parents/grand parents
           if Ga < len(_PARENTS_LEVEL):
               rel_str = _PARENTS_LEVEL[Ga]
           else:
               rel_str = "אבות־קדמונים רחוקים"
       elif Gb == 1:
           # These are siblings/aunts/uncles
           if Ga < len(_SIBLINGS_LEVEL):
               rel_str = _SIBLINGS_LEVEL[Ga]
           else:
               rel_str = "דודים/דודות רחוקים"
       elif Ga == 1:
           # These are nieces/nephews
           if Gb < len(_NEPHEWS_NIECES_LEVEL):
               rel_str = _NEPHEWS_NIECES_LEVEL[Gb]
           else:
               rel_str = "אחיינים/אחייניות רחוקים"
       elif Ga > 1 and Ga == Gb:
           # These are cousins in the same generation
           if Ga <= len(_LEVEL_NAME):
               rel_str = "בני דודים %s  " % _LEVEL_NAME[Ga - 1]
           else:
               rel_str = "בני דודים רחוקים"
       elif Ga > 1 and Ga > Gb:
           # These are cousins in different generations with the second person
           # being in a higher generation from the common ancestor than the
           # first person.
           if Gb <= len(_LEVEL_NAME) and (Ga - Gb) < len(_REMOVED_LEVEL):
               rel_str = " %s %s (עולה)" % (
                   _LEVEL_NAME[Gb - 1],
                   _REMOVED_LEVEL[Ga - Gb],
               )
           else:
               rel_str = "בני דודים רחוקים"
       elif Gb > 1 and Gb > Ga:
           # These are cousins in different generations with the second person
           # being in a lower generation from the common ancestor than the
           # first person.
           if Ga <= len(_LEVEL_NAME) and (Gb - Ga) < len(_REMOVED_LEVEL):
               rel_str = " בני דודים%s %s (יורד)" % (
                   _LEVEL_NAME[Ga - 1],
                   _REMOVED_LEVEL[Gb - Ga],
               )
           else:
               rel_str = "בני דודים רחוקים"
       if in_law_b is True:
           rel_str = "זוג של %s" % rel_str
       return rel_str
   def get_single_relationship_string(
       self,
       Ga,
       Gb,
       gender_a,
       gender_b,
       reltocommon_a,
       reltocommon_b,
       only_birth=True,
       in_law_a=False,
       in_law_b=False,
   ):
       """
       Provide a string that describes the relationsip between a person, and
       another person. E.g. "grandparent" or "child".
       To be used as: 'person b is the grandparent of a', this will be in
       translation string:  'person b is the %(relation)s of a'
       Note that languages with gender should add 'the' inside the
       translation, so eg in french:  'person b est %(relation)s de a'
       where relation will be here: le grandparent
       Ga and Gb can be used to mathematically calculate the relationship.
       .. seealso::
           http://en.wikipedia.org/wiki/Cousin#Mathematical_definitions
       Some languages need to know the specific path to the common ancestor.
       Those languages should use reltocommon_a and reltocommon_b which is
       a string like 'mfmf'.
       The possible string codes are:
       =======================  ===========================================
       Code                     Description
       =======================  ===========================================
       REL_MOTHER               # going up to mother
       REL_FATHER               # going up to father
       REL_MOTHER_NOTBIRTH      # going up to mother, not birth relation
       REL_FATHER_NOTBIRTH      # going up to father, not birth relation
       REL_FAM_BIRTH            # going up to family (mother and father)
       REL_FAM_NONBIRTH         # going up to family, not birth relation
       REL_FAM_BIRTH_MOTH_ONLY  # going up to fam, only birth rel to mother
       REL_FAM_BIRTH_FATH_ONLY  # going up to fam, only birth rel to father
       =======================  ===========================================
       Prefix codes are stripped, so REL_FAM_INLAW_PREFIX is not present.
       If the relation starts with the inlaw of the person a, then 'in_law_a'
       is True, if it starts with the inlaw of person b, then 'in_law_b' is
       True.
       Also REL_SIBLING (# going sideways to sibling (no parents)) is not
       passed to this routine. The collapse_relations changes this to a
       family relation.
       Hence, calling routines should always strip REL_SIBLING and
       REL_FAM_INLAW_PREFIX before calling get_single_relationship_string()
       Note that only_birth=False, means that in the reltocommon one of the
       NOTBIRTH specifiers is present.
       The REL_FAM identifiers mean that the relation is not via a common
       ancestor, but via a common family (note that that is not possible for
       direct descendants or direct ancestors!). If the relation to one of the
       parents in that common family is by birth, then 'only_birth' is not
       set to False. The only_birth() method is normally used for this.
       :param Ga: The number of generations between the main person and the
                  common ancestor.
       :type Ga: int
       :param Gb: The number of generations between the other person and the
                  common ancestor.
       :type Gb: int
       :param gender_a: gender of person a
       :type gender_a: int gender
       :param gender_b: gender of person b
       :type gender_b: int gender
       :param reltocommon_a: relation path to common ancestor or common
                             Family for person a.
                             Note that length = Ga
       :type reltocommon_a: str
       :param reltocommon_b: relation path to common ancestor or common
                             Family for person b.
                             Note that length = Gb
       :type reltocommon_b: str
       :param in_law_a:  True if path to common ancestors is via the partner
                         of person a
       :type in_law_a: bool
       :param in_law_b: True if path to common ancestors is via the partner
                        of person b
       :type in_law_b: bool
       :param only_birth: True if relation between a and b is by birth only
                          False otherwise
       :type only_birth: bool
       :returns: A string describing the relationship between the two people
       :rtype: str
       .. note:: 1. the self.REL_SIBLING should not be passed to this routine,
                    so we should not check on it. All other self.
                 2. for better determination of siblings, use if Ga=1=Gb
                    get_sibling_relationship_string
       """
       if only_birth:
           step = ""
       else:
           if gender_b == MALE:
               step = self.STEP
           elif gender_b == FEMALE:
               step = self.STEP_F
           else:
               step = self.STEP # Change this as appropriate for other and unknown gender
       if in_law_a or in_law_b:
           inlaw = self.INLAW
       else:
           inlaw = ""
       rel_str = "קרוב־משפחה רחוק %s%s" % (step, inlaw)
       if Ga == 0:
           # b is descendant of a
           if Gb == 0:
               rel_str = "האדם"
           elif gender_b == MALE:
               rel_str = self._get_son(Gb, step, inlaw)
           elif gender_b == FEMALE:
               rel_str = self._get_daughter(Gb, step, inlaw)
           else:
               rel_str = self._get_child_unknown(Gb, step, inlaw)
       elif Gb == 0:
           # b is parents/grand parent of a
           if gender_b == MALE:
               rel_str = self._get_father(Ga, step, inlaw)
           elif gender_b == FEMALE:
               rel_str = self._get_mother(Ga, step, inlaw)
           else:
               rel_str = self._get_parent_unknown(Ga, step, inlaw)
       elif Gb == 1:
           # b is sibling/aunt/uncle of a
           if gender_b == MALE:
               rel_str = self._get_uncle(Ga, step, inlaw)
           elif gender_b == FEMALE:
               rel_str = self._get_aunt(Ga, step, inlaw)
           else:
               rel_str = self._get_sibling(Ga, step, inlaw)
       elif Ga == 1:
           # b is niece/nephew of a
           if gender_b == MALE:
               rel_str = self._get_nephew(Gb - 1, step, inlaw)
           elif gender_b == FEMALE:
               rel_str = self._get_niece(Gb - 1, step, inlaw)
           elif Gb < len(_NIECE_LEVEL) and Gb < len(_NEPHEW_LEVEL):
               rel_str = "%sאו %s" % (
                   self._get_nephew(Gb - 1, step, inlaw),
                   self._get_niece(Gb - 1, step, inlaw),
               )
           else:
               rel_str = "אחיין/אחיינית רחוקים %s %s" % (step, inlaw)
       elif Ga == Gb:
           # a and b cousins in the same generation
           rel_str = self._get_cousin(Ga - 1, 0, dir="", step=step, inlaw=inlaw)
       elif Ga > Gb:
           # These are cousins in different generations with the second person
           # being in a higher generation from the common ancestor than the
           # first person.
           rel_str = self._get_cousin(
               Gb - 1, Ga - Gb, dir=" (עולה)", step=step, inlaw=inlaw
           )
       elif Gb > Ga:
           # These are cousins in different generations with the second person
           # being in a lower generation from the common ancestor than the
           # first person.
           rel_str = self._get_cousin(
               Ga - 1, Gb - Ga, dir=" (יורד)", step=step, inlaw=inlaw
           )
       return rel_str
   def get_sibling_relationship_string(
       self, sib_type, gender_a, gender_b, in_law_a=False, in_law_b=False
   ):
       """
       Determine the string giving the relation between two siblings of
       type sib_type.
       Eg: b is the brother of a
       Here 'brother' is the string we need to determine
       This method gives more details about siblings than
       get_single_relationship_string can do.
       .. warning:: DON'T TRANSLATE THIS PROCEDURE IF LOGIC IS EQUAL IN YOUR
                    LANGUAGE, AND SAME METHODS EXIST (get_uncle, get_aunt,
                    get_sibling)
       """
       if sib_type == self.NORM_SIB or sib_type == self.UNKNOWN_SIB:
           typestr = ""
       elif sib_type == self.HALF_SIB_MOTHER or sib_type == self.HALF_SIB_FATHER:
           typestr = self.HALF
       elif sib_type == self.STEP_SIB:
           typestr = self.STEP
       if in_law_a or in_law_b:
           inlaw = self.INLAW
       else:
           inlaw = ""
       if gender_b == MALE:
           rel_str = self._get_uncle(1, typestr, inlaw)
       elif gender_b == FEMALE:
           rel_str = self._get_aunt(1, typestr, inlaw)
       else:
           rel_str = self._get_sibling(1, typestr, inlaw)
       return rel_str
   def get_partner_relationship_string(self, spouse_type, gender_a, gender_b):
       """
       Determine the string giving the relation between two partners of
       type spouse_type.
       Eg: b is the spouse of a
       Here 'spouse' is the string we need to determine
       .. warning:: DON'T TRANSLATE THIS PROCEDURE IF LOGIC IS EQUAL IN YOUR
                    LANGUAGE, AS GETTEXT IS ALREADY USED !
       """
       # english only needs gender of b, we don't guess if unknown like in old
       # procedure as that is stupid in present day cases!
       gender = gender_b
       if not spouse_type:
           return ""
       trans_text = _
       # trans_text is a defined keyword (see po/update_po.py, po/genpot.sh)
       if hasattr(self, "_locale") and self._locale != glocale:
           trans_text = self._locale.translation.sgettext
       if spouse_type == self.PARTNER_MARRIED:
           if gender == MALE:
               return trans_text("בעל")
           elif gender == FEMALE:
               return trans_text("רעיה")
           else:
               return trans_text("זוג", "מגדר לא ידוע")
       elif spouse_type == self.PARTNER_EX_MARRIED:
           if gender == MALE:
               return trans_text("בעל לשעבר")
           elif gender == FEMALE:
               return trans_text("רעיה לשעבר")
           else:
               return trans_text("זוג לשעבר", "מגדר לא ידוע")
       elif spouse_type == self.PARTNER_UNMARRIED:
           if gender == MALE:
               return trans_text("שותף", "זכר, לא נשוי")
           elif gender == FEMALE:
               return trans_text("שותפה", "נקבה, לא נשואה")
           else:
               return trans_text("שותף", "מגדר לא ידוע, לא נשוי")
       elif spouse_type == self.PARTNER_EX_UNMARRIED:
           if gender == MALE:
               return trans_text("שותף לשעבר", "זכר, לא נשוי")
           elif gender == FEMALE:
               return trans_text("שותפה לשעבר", "נקבה, לא נשואה")
           else:
               return trans_text("שותף לשעבר", "מגדר לא ידוע, לא נשוי")
       elif spouse_type == self.PARTNER_CIVIL_UNION:
           if gender == MALE:
               return trans_text("שותף", "זכר, נישואים אזרחיים")
           elif gender == FEMALE:
               return trans_text("שותפה", "נקבה, נישואים אזרחיים")
           else:
               return trans_text("שותף", "מגדר לא ידוע, נישואים אזרחיים")
       elif spouse_type == self.PARTNER_EX_CIVIL_UNION:
           if gender == MALE:
               return trans_text("שותף קודם", "זכר, נישואים אזרחיים")
           elif gender == FEMALE:
               return trans_text("שותפה קודמת", "נקבה, נישואים אזרחיים")
           else:
               return trans_text("שותף קודם", "מגדר לא ידוע ,נישואים אזרחיים")
       elif spouse_type == self.PARTNER_UNKNOWN_REL:
           if gender == MALE:
               return trans_text("שותף", "זכר, טיב יחסים לא ידוע")
           elif gender == FEMALE:
               return trans_text("שותפה", "נקבה, טיב יחסים לא ידוע")
           else:
               return trans_text("שותף", "מגדר לא ידוע, טיב יחסים לא ידוע")
       else:
           # here we have spouse_type == self.PARTNER_EX_UNKNOWN_REL
           #   or other not catched types
           if gender == MALE:
               return trans_text("שותף קודם", "זכר, טיב יחסים לא ידוע")
           elif gender == FEMALE:
               return trans_text("שותפה קודמת", "נקבה, טיב יחסים לא ידוע")
           else:
               return trans_text("שותף קודם", "מגדר לא ידוע ,טיב יחסים לא ידוע")
   def connect_db_signals(self, dbstate):
       """
       We can save work by storing a map, however, if database changes
       this map must be regenerated.
       Before close, the calling app must call disconnect_db_signals
       """
       if self.__db_connected:
           return
       assert len(self.signal_keys) == 0
       self.state_signal_key = dbstate.connect(
           "database-changed", self._dbchange_callback
       )
       self.__connect_db_signals(dbstate.db)
   def __connect_db_signals(self, db):
       signals = [
           "person-add",
           "person-update",
           "person-delete",
           "person-rebuild",
           "family-add",
           "family-update",
           "family-delete",
           "family-rebuild",
           "database-changed",
       ]
       for name in signals:
           self.signal_keys.append(db.connect(name, self._datachange_callback))
       self.storemap = True
       self.__db_connected = True
   def disconnect_db_signals(self, dbstate):
       """
       Method to disconnect to all signals the relationship calculator is
       subscribed
       """
       dbstate.disconnect(self.state_signal_key)
       list(map(dbstate.db.disconnect, self.signal_keys))
       self.storemap = False
       self.stored_map = None
   def _dbchange_callback(self, db):
       """
       When database changes, the map can no longer be used.
       Connects must be remade
       """
       self.dirtymap = True
       # signals are disconnected on close of old database, connect to new
       self.__connect_db_signals(db)
   def _datachange_callback(self, handle_list=None):
       """
       When data in database changes, the map can no  longer be used.
       As the map might be in use or might be generated at the moment,
       this method sets a dirty flag. Before reusing the map, this flag
       will be checked
       """
       self.dirtymap = True


  1. -------------------------------------------------------------------------
  2. define the default relationshipcalculator
  3. -------------------------------------------------------------------------

__RELCALC_CLASS = None


def get_relationship_calculator(reinit=False, clocale=glocale):

   """
   Return the relationship calculator for the current language.
   If clocale is passed in (a GrampsLocale) then that language will be used.
   :param clocale: allow selection of the relationship language
   :type clocale: a GrampsLocale instance
   """
   global __RELCALC_CLASS
   if __RELCALC_CLASS is None or reinit:
       lang = clocale.language[0]
       __RELCALC_CLASS = RelationshipCalculator
       # If lang not set default to English relationship calulator
       # See if lang begins with en_, English_ or english_
       # If so return standard relationship calculator.
       if lang.startswith("en") or lang == "C":
           return __RELCALC_CLASS()
       # set correct non English relationship calculator based on lang
       relation_translation_found = False
       for plugin in PluginRegister.get_instance().relcalc_plugins():
           if lang in plugin.lang_list:
               pmgr = BasePluginManager.get_instance()
               # the loaded module is put in variable mod
               mod = pmgr.load_plugin(plugin)
               if mod:
                   __RELCALC_CLASS = getattr(mod, plugin.relcalcclass)
                   relation_translation_found = True
                   break
       if not relation_translation_found and len(
           PluginRegister.get_instance().relcalc_plugins()
       ):
           LOG.warning(
               _(
                   "תרגומון קירבת משפחה לא זמין "
                   "לקוד השפה '%s'. במקום זאת גרמפס יאתחל בשפה ה'אנגלית'."
               ),
               lang,
           )
   return __RELCALC_CLASS()


if __name__ == "__main__":

   """
   TRANSLATORS, copy this if statement at the bottom of your
   rel_xx.py module, after adding: 'from Relationship import test'
   and test your work with:
   export PYTHONPATH=/path/to/gramps/src
   python src/plugins/rel_xx.py
   See eg rel_fr.py at the bottom
   """
   from gramps.gen.relationship import test
   REL_CALC = RelationshipCalculator()
   test(REL_CALC, True)