1#
2# Gramps - a GTK+/GNOME based genealogy program
3#
4# Copyright (C) 2003-2005  Donald N. Allingham
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20"""
21Classes for relationships.
22"""
23
24#-------------------------------------------------------------------------
25#
26# Python modules
27#
28#-------------------------------------------------------------------------
29import logging
30
31#-------------------------------------------------------------------------
32#
33# Gramps modules
34#
35#-------------------------------------------------------------------------
36from .lib import Person, ChildRefType, EventType, FamilyRelType
37from .plug import PluginRegister, BasePluginManager
38from .const import GRAMPS_LOCALE as glocale
39_ = glocale.translation.sgettext
40
41MALE = Person.MALE
42FEMALE = Person.FEMALE
43UNKNOWN = Person.UNKNOWN
44
45LOG = logging.getLogger("gen.relationship")
46LOG.addHandler(logging.StreamHandler())
47
48#-------------------------------------------------------------------------
49#
50#
51#
52#-------------------------------------------------------------------------
53
54_LEVEL_NAME = ["", "first", "second", "third", "fourth", "fifth", "sixth",
55               "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth",
56               "thirteenth", "fourteenth", "fifteenth", "sixteenth",
57               "seventeenth", "eighteenth", "nineteenth", "twentieth",
58               "twenty-first", "twenty-second", "twenty-third", "twenty-fourth",
59               "twenty-fifth", "twenty-sixth", "twenty-seventh", "twenty-eighth",
60               "twenty-ninth", "thirtieth", "thirty-first", "thirty-second",
61               "thirty-third", "thirty-fourth", "thirty-fifth", "thirty-sixth",
62               "thirty-seventh", "thirty-eighth", "thirty-ninth", "fortieth",
63               "forty-first", "forty-second", "forty-third", "forty-fourth",
64               "forty-fifth", "forty-sixth", "forty-seventh", "forty-eighth",
65               "forty-ninth", "fiftieth" ]
66
67_REMOVED_LEVEL = ["", " once removed", " twice removed",
68                  " three times removed",
69                  " four times removed", " five times removed",
70                  " six times removed",
71                  " seven times removed", " eight times removed",
72                  " nine times removed",
73                  " ten times removed", " eleven times removed",
74                  " twelve times removed",
75                  " thirteen times removed", " fourteen times removed",
76                  " fifteen times removed",
77                  " sixteen times removed", " seventeen times removed",
78                  " eighteen times removed",
79                  " nineteen times removed", " twenty times removed",
80                  " twenty-one times removed", " twenty-two times removed",
81                  " twenty-three times removed", " twenty-four times removed",
82                  " twenty-five times removed", " twenty-six times removed",
83                  " twenty-seven times removed", " twenty-eight times removed",
84                  " twenty-nine times removed", " thirty times removed",
85                  " thirty-one times removed", " thirty-two times removed",
86                  " thirty-three times removed", " thirty-four times removed",
87                  " thirty-five times removed", " thirty-six times removed",
88                  " thirty-seven times removed", " thirty-eight times removed",
89                  " thirty-nine times removed", " forty times removed",
90                  " forty-one times removed", " forty-two times removed",
91                  " forty-three times removed", " forty-four times removed",
92                  " forty-five times removed", " forty-six times removed",
93                  " forty-seven times removed", " forty-eight times removed",
94                  " forty-nine times removed", " fifty times removed", ]
95
96_PARENTS_LEVEL = ["", "parents", "grandparents", "great grandparents",
97                  "second great grandparents",
98                  "third great grandparents",
99                  "fourth great grandparents",
100                  "fifth great grandparents",
101                  "sixth great grandparents",
102                  "seventh great grandparents",
103                  "eighth great grandparents",
104                  "ninth great grandparents",
105                  "tenth great grandparents",
106                  "eleventh great grandparents",
107                  "twelfth great grandparents",
108                  "thirteenth great grandparents",
109                  "fourteenth great grandparents",
110                  "fifteenth great grandparents",
111                  "sixteenth great grandparents",
112                  "seventeenth great grandparents",
113                  "eighteenth great grandparents",
114                  "nineteenth great grandparents",
115                  "twentieth great grandparents",
116                  "twenty-first great grandparents",
117                  "twenty-second great grandparents",
118                  "twenty-third great grandparents",
119                  "twenty-fourth great grandparents",
120                  "twenty-fifth great grandparents",
121                  "twenty-sixth great grandparents",
122                  "twenty-seventh great grandparents",
123                  "twenty-eighth great grandparents",
124                  "twenty-ninth great grandparents",
125                  "thirtieth great grandparents",
126                  "thirty-first great grandparents",
127                  "thirty-second great grandparents",
128                  "thirty-third great grandparents",
129                  "thirty-fourth great grandparents",
130                  "thirty-fifth great grandparents",
131                  "thirty-sixth great grandparents",
132                  "thirty-seventh great grandparents",
133                  "thirty-eighth great grandparents",
134                  "thirty-ninth great grandparents",
135                  "fortieth great grandparents",
136                  "forty-first great grandparents",
137                  "forty-second great grandparents",
138                  "forty-third great grandparents",
139                  "forty-fourth great grandparents",
140                  "forty-fifth great grandparents",
141                  "forty-sixth great grandparents",
142                  "forty-seventh great grandparents",
143                  "forty-eighth great grandparents",
144                  "forty-ninth great grandparents",
145                  "fiftieth great grandparents", ]
146
147_FATHER_LEVEL = ["", "%(step)sfather%(inlaw)s", "%(step)sgrandfather%(inlaw)s",
148                 "great %(step)sgrandfather%(inlaw)s",
149                 "second great %(step)sgrandfather%(inlaw)s",
150                 "third great %(step)sgrandfather%(inlaw)s",
151                 "fourth great %(step)sgrandfather%(inlaw)s",
152                 "fifth great %(step)sgrandfather%(inlaw)s",
153                 "sixth great %(step)sgrandfather%(inlaw)s",
154                 "seventh great %(step)sgrandfather%(inlaw)s",
155                 "eighth great %(step)sgrandfather%(inlaw)s",
156                 "ninth great %(step)sgrandfather%(inlaw)s",
157                 "tenth great %(step)sgrandfather%(inlaw)s",
158                 "eleventh great %(step)sgrandfather%(inlaw)s",
159                 "twelfth great %(step)sgrandfather%(inlaw)s",
160                 "thirteenth great %(step)sgrandfather%(inlaw)s",
161                 "fourteenth great %(step)sgrandfather%(inlaw)s",
162                 "fifteenth great %(step)sgrandfather%(inlaw)s",
163                 "sixteenth great %(step)sgrandfather%(inlaw)s",
164                 "seventeenth great %(step)sgrandfather%(inlaw)s",
165                 "eighteenth great %(step)sgrandfather%(inlaw)s",
166                 "nineteenth great %(step)sgrandfather%(inlaw)s",
167                 "twentieth great %(step)sgrandfather%(inlaw)s",
168                 "twenty-first great %(step)sgrandfather%(inlaw)s",
169                 "twenty-second great %(step)sgrandfather%(inlaw)s",
170                 "twenty-third great %(step)sgrandfather%(inlaw)s",
171                 "twenty-fourth great %(step)sgrandfather%(inlaw)s",
172                 "twenty-fifth great %(step)sgrandfather%(inlaw)s",
173                 "twenty-sixth great %(step)sgrandfather%(inlaw)s",
174                 "twenty-seventh great %(step)sgrandfather%(inlaw)s",
175                 "twenty-eighth great %(step)sgrandfather%(inlaw)s",
176                 "twenty-ninth great %(step)sgrandfather%(inlaw)s",
177                 "thirtieth great %(step)sgrandfather%(inlaw)s",
178                 "thirty-first great %(step)sgrandfather%(inlaw)s",
179                 "thirty-second great %(step)sgrandfather%(inlaw)s",
180                 "thirty-third great %(step)sgrandfather%(inlaw)s",
181                 "thirty-fourth great %(step)sgrandfather%(inlaw)s",
182                 "thirty-fifth great %(step)sgrandfather%(inlaw)s",
183                 "thirty-sixth great %(step)sgrandfather%(inlaw)s",
184                 "thirty-seventh great %(step)sgrandfather%(inlaw)s",
185                 "thirty-eighth great %(step)sgrandfather%(inlaw)s",
186                 "thirty-ninth great %(step)sgrandfather%(inlaw)s",
187                 "fourtieth great %(step)sgrandfather%(inlaw)s",
188                 "forty-first great %(step)sgrandfather%(inlaw)s",
189                 "forty-second great %(step)sgrandfather%(inlaw)s",
190                 "forty-third great %(step)sgrandfather%(inlaw)s",
191                 "forty-fourth great %(step)sgrandfather%(inlaw)s",
192                 "forty-fifth great %(step)sgrandfather%(inlaw)s",
193                 "forty-sixth great %(step)sgrandfather%(inlaw)s",
194                 "forty-seventh great %(step)sgrandfather%(inlaw)s",
195                 "forty-eighth great %(step)sgrandfather%(inlaw)s",
196                 "forty-ninth great %(step)sgrandfather%(inlaw)s",
197                 "fiftieth great %(step)sgrandfather%(inlaw)s", ]
198
199_MOTHER_LEVEL = ["", "%(step)smother%(inlaw)s",
200                 "%(step)sgrandmother%(inlaw)s",
201                 "great %(step)sgrandmother%(inlaw)s",
202                 "second great %(step)sgrandmother%(inlaw)s",
203                 "third great %(step)sgrandmother%(inlaw)s",
204                 "fourth great %(step)sgrandmother%(inlaw)s",
205                 "fifth great %(step)sgrandmother%(inlaw)s",
206                 "sixth great %(step)sgrandmother%(inlaw)s",
207                 "seventh great %(step)sgrandmother%(inlaw)s",
208                 "eighth great %(step)sgrandmother%(inlaw)s",
209                 "ninth great %(step)sgrandmother%(inlaw)s",
210                 "tenth great %(step)sgrandmother%(inlaw)s",
211                 "eleventh great %(step)sgrandmother%(inlaw)s",
212                 "twelfth great %(step)sgrandmother%(inlaw)s",
213                 "thirteenth great %(step)sgrandmother%(inlaw)s",
214                 "fourteenth great %(step)sgrandmother%(inlaw)s",
215                 "fifteenth great %(step)sgrandmother%(inlaw)s",
216                 "sixteenth great %(step)sgrandmother%(inlaw)s",
217                 "seventeenth great %(step)sgrandmother%(inlaw)s",
218                 "eighteenth great %(step)sgrandmother%(inlaw)s",
219                 "nineteenth great %(step)sgrandmother%(inlaw)s",
220                 "twentieth great %(step)sgrandmother%(inlaw)s",
221                 "twenty-first great %(step)sgrandmother%(inlaw)s",
222                 "twenty-second great %(step)sgrandmother%(inlaw)s",
223                 "twenty-third great %(step)sgrandmother%(inlaw)s",
224                 "twenty-fourth great %(step)sgrandmother%(inlaw)s",
225                 "twenty-fifth great %(step)sgrandmother%(inlaw)s",
226                 "twenty-sixth great %(step)sgrandmother%(inlaw)s",
227                 "twenty-seventh great %(step)sgrandmother%(inlaw)s",
228                 "twenty-eighth great %(step)sgrandmother%(inlaw)s",
229                 "twenty-ninth great %(step)sgrandmother%(inlaw)s",
230                 "thirtieth great %(step)sgrandmother%(inlaw)s",
231                 "thirty-first great %(step)sgrandmother%(inlaw)s",
232                 "thirty-second great %(step)sgrandmother%(inlaw)s",
233                 "thirty-third great %(step)sgrandmother%(inlaw)s",
234                 "thirty-forth great %(step)sgrandmother%(inlaw)s",
235                 "thirty-fifth great %(step)sgrandmother%(inlaw)s",
236                 "thirty-sixth great %(step)sgrandmother%(inlaw)s",
237                 "thirty-seventh great %(step)sgrandmother%(inlaw)s",
238                 "thirty-eighth great %(step)sgrandmother%(inlaw)s",
239                 "thirty-ninth great %(step)sgrandmother%(inlaw)s",
240                 "fourtieth great %(step)sgrandmother%(inlaw)s",
241                 "forty-first great %(step)sgrandmother%(inlaw)s",
242                 "forty-second great %(step)sgrandmother%(inlaw)s",
243                 "forty-third great %(step)sgrandmother%(inlaw)s",
244                 "forty-fourth great %(step)sgrandmother%(inlaw)s",
245                 "forty-fifth great %(step)sgrandmother%(inlaw)s",
246                 "forty-sixth great %(step)sgrandmother%(inlaw)s",
247                 "forty-seventh great %(step)sgrandmother%(inlaw)s",
248                 "forty-eighth great %(step)sgrandmother%(inlaw)s",
249                 "forty-ninth great %(step)sgrandmother%(inlaw)s",
250                 "fiftieth great %(step)sgrandmother%(inlaw)s", ]
251
252_SON_LEVEL = ["", "%(step)sson%(inlaw)s", "%(step)sgrandson%(inlaw)s",
253              "great %(step)sgrandson%(inlaw)s",
254              "second great %(step)sgrandson%(inlaw)s",
255              "third great %(step)sgrandson%(inlaw)s",
256              "fourth great %(step)sgrandson%(inlaw)s",
257              "fifth great %(step)sgrandson%(inlaw)s",
258              "sixth great %(step)sgrandson%(inlaw)s",
259              "seventh great %(step)sgrandson%(inlaw)s",
260              "eighth great %(step)sgrandson%(inlaw)s",
261              "ninth great %(step)sgrandson%(inlaw)s",
262              "tenth great %(step)sgrandson%(inlaw)s",
263              "eleventh great %(step)sgrandson%(inlaw)s",
264              "twelfth great %(step)sgrandson%(inlaw)s",
265              "thirteenth great %(step)sgrandson%(inlaw)s",
266              "fourteenth great %(step)sgrandson%(inlaw)s",
267              "fifteenth great %(step)sgrandson%(inlaw)s",
268              "sixteenth great %(step)sgrandson%(inlaw)s",
269              "seventeenth great %(step)sgrandson%(inlaw)s",
270              "eighteenth great %(step)sgrandson%(inlaw)s",
271              "nineteenth great %(step)sgrandson%(inlaw)s",
272              "twentieth great %(step)sgrandson%(inlaw)s",
273              "twenty-first great %(step)sgrandson%(inlaw)s",
274              "twenty-second great %(step)sgrandson%(inlaw)s",
275              "twenty-third great %(step)sgrandson%(inlaw)s",
276              "twenty-fourth great %(step)sgrandson%(inlaw)s",
277              "twenty-fifth great %(step)sgrandson%(inlaw)s",
278              "twenty-sixth great %(step)sgrandson%(inlaw)s",
279              "twenty-seventh great %(step)sgrandson%(inlaw)s",
280              "twenty-eighth great %(step)sgrandson%(inlaw)s",
281              "twenty-ninth great %(step)sgrandson%(inlaw)s",
282              "thirtieth great %(step)sgrandson%(inlaw)s",
283              "thirty-first great %(step)sgrandson%(inlaw)s",
284              "thirty-second great %(step)sgrandson%(inlaw)s",
285              "thirty-third great %(step)sgrandson%(inlaw)s",
286              "thirty-forth great %(step)sgrandson%(inlaw)s",
287              "thirty-fifth great %(step)sgrandson%(inlaw)s",
288              "thirty-sixth great %(step)sgrandson%(inlaw)s",
289              "thirty-seventh great %(step)sgrandson%(inlaw)s",
290              "thirty-eighth great %(step)sgrandson%(inlaw)s",
291              "thirty-ninth great %(step)sgrandson%(inlaw)s",
292              "fourtieth great %(step)sgrandson%(inlaw)s",
293              "forty-first great %(step)sgrandson%(inlaw)s",
294              "forty-second great %(step)sgrandson%(inlaw)s",
295              "forty-third great %(step)sgrandson%(inlaw)s",
296              "forty-fourth great %(step)sgrandson%(inlaw)s",
297              "forty-fifth great %(step)sgrandson%(inlaw)s",
298              "forty-sixth great %(step)sgrandson%(inlaw)s",
299              "forty-seventh great %(step)sgrandson%(inlaw)s",
300              "forty-eighth great %(step)sgrandson%(inlaw)s",
301              "forty-ninth great %(step)sgrandson%(inlaw)s",
302              "fiftieth great %(step)sgrandson%(inlaw)s", ]
303
304_DAUGHTER_LEVEL = ["", "%(step)sdaughter%(inlaw)s",
305                   "%(step)sgranddaughter%(inlaw)s",
306                   "great %(step)sgranddaughter%(inlaw)s",
307                   "second great %(step)sgranddaughter%(inlaw)s",
308                   "third great %(step)sgranddaughter%(inlaw)s",
309                   "fourth great %(step)sgranddaughter%(inlaw)s",
310                   "fifth great %(step)sgranddaughter%(inlaw)s",
311                   "sixth great %(step)sgranddaughter%(inlaw)s",
312                   "seventh great %(step)sgranddaughter%(inlaw)s",
313                   "eighth great %(step)sgranddaughter%(inlaw)s",
314                   "ninth great %(step)sgranddaughter%(inlaw)s",
315                   "tenth great %(step)sgranddaughter%(inlaw)s",
316                   "eleventh great %(step)sgranddaughter%(inlaw)s",
317                   "twelfth great %(step)sgranddaughter%(inlaw)s",
318                   "thirteenth great %(step)sgranddaughter%(inlaw)s",
319                   "fourteenth great %(step)sgranddaughter%(inlaw)s",
320                   "fifteenth great %(step)sgranddaughter%(inlaw)s",
321                   "sixteenth great %(step)sgranddaughter%(inlaw)s",
322                   "seventeenth great %(step)sgranddaughter%(inlaw)s",
323                   "eighteenth great %(step)sgranddaughter%(inlaw)s",
324                   "nineteenth great %(step)sgranddaughter%(inlaw)s",
325                   "twentieth great %(step)sgranddaughter%(inlaw)s",
326                   "twenty-first great %(step)sgranddaughter%(inlaw)s",
327                   "twenty-second great %(step)sgranddaughter%(inlaw)s",
328                   "twenty-third great %(step)sgranddaughter%(inlaw)s",
329                   "twenty-fourth great %(step)sgranddaughter%(inlaw)s",
330                   "twenty-fifth great %(step)sgranddaughter%(inlaw)s",
331                   "twenty-sixth great %(step)sgranddaughter%(inlaw)s",
332                   "twenty-seventh great %(step)sgranddaughter%(inlaw)s",
333                   "twenty-eighth great %(step)sgranddaughter%(inlaw)s",
334                   "twenty-ninth great %(step)sgranddaughter%(inlaw)s",
335                   "thirtieth great %(step)sgranddaughter%(inlaw)s",
336                   "thirty-first great %(step)sgranddaughter%(inlaw)s",
337                   "thirty-second great %(step)sgranddaughter%(inlaw)s",
338                   "thirty-third great %(step)sgranddaughter%(inlaw)s",
339                   "thirty-forth great %(step)sgranddaughter%(inlaw)s",
340                   "thirty-fifth great %(step)sgranddaughter%(inlaw)s",
341                   "thirty-sixth great %(step)sgranddaughter%(inlaw)s",
342                   "thirty-seventh great %(step)sgranddaughter%(inlaw)s",
343                   "thirty-eighth great %(step)sgranddaughter%(inlaw)s",
344                   "thirty-ninth great %(step)sgranddaughter%(inlaw)s",
345                   "fourtieth great %(step)sgranddaughter%(inlaw)s",
346                   "forty-first great %(step)sgranddaughter%(inlaw)s",
347                   "forty-second great %(step)sgranddaughter%(inlaw)s",
348                   "forty-third great %(step)sgranddaughter%(inlaw)s",
349                   "forty-fourth great %(step)sgranddaughter%(inlaw)s",
350                   "forty-fifth great %(step)sgranddaughter%(inlaw)s",
351                   "forty-sixth great %(step)sgranddaughter%(inlaw)s",
352                   "forty-seventh great %(step)sgranddaughter%(inlaw)s",
353                   "forty-eighth great %(step)sgranddaughter%(inlaw)s",
354                   "forty-ninth great %(step)sgranddaughter%(inlaw)s",
355                   "fiftieth great %(step)sgranddaughter%(inlaw)s", ]
356
357_SISTER_LEVEL = ["", "%(step)ssister%(inlaw)s", "%(step)saunt%(inlaw)s",
358                 "%(step)sgrandaunt%(inlaw)s",
359                 "great %(step)sgrandaunt%(inlaw)s",
360                 "second great %(step)sgrandaunt%(inlaw)s",
361                 "third great %(step)sgrandaunt%(inlaw)s",
362                 "fourth great %(step)sgrandaunt%(inlaw)s",
363                 "fifth great %(step)sgrandaunt%(inlaw)s",
364                 "sixth great %(step)sgrandaunt%(inlaw)s",
365                 "seventh great %(step)sgrandaunt%(inlaw)s",
366                 "eighth great %(step)sgrandaunt%(inlaw)s",
367                 "ninth great %(step)sgrandaunt%(inlaw)s",
368                 "tenth great %(step)sgrandaunt%(inlaw)s",
369                 "eleventh great %(step)sgrandaunt%(inlaw)s",
370                 "twelfth great %(step)sgrandaunt%(inlaw)s",
371                 "thirteenth great %(step)sgrandaunt%(inlaw)s",
372                 "fourteenth great %(step)sgrandaunt%(inlaw)s",
373                 "fifteenth great %(step)sgrandaunt%(inlaw)s",
374                 "sixteenth great %(step)sgrandaunt%(inlaw)s",
375                 "seventeenth great %(step)sgrandaunt%(inlaw)s",
376                 "eighteenth great %(step)sgrandaunt%(inlaw)s",
377                 "nineteenth great %(step)sgrandaunt%(inlaw)s",
378                 "twentieth great %(step)sgrandaunt%(inlaw)s",
379                 "twenty-first great %(step)sgrandaunt%(inlaw)s",
380                 "twenty-second great %(step)sgrandaunt%(inlaw)s",
381                 "twenty-third great %(step)sgrandaunt%(inlaw)s",
382                 "twenty-fourth great %(step)sgrandaunt%(inlaw)s",
383                 "twenty-fifth great %(step)sgrandaunt%(inlaw)s",
384                 "twenty-sixth great %(step)sgrandaunt%(inlaw)s",
385                 "twenty-seventh great %(step)sgrandaunt%(inlaw)s",
386                 "twenty-eighth great %(step)sgrandaunt%(inlaw)s",
387                 "twenty-ninth great %(step)sgrandaunt%(inlaw)s",
388                 "thirtieth great %(step)sgrandaunt%(inlaw)s",
389                 "thirty-first great %(step)sgrandaunt%(inlaw)s",
390                 "thirty-second great %(step)sgrandaunt%(inlaw)s",
391                 "thirty-third great %(step)sgrandaunt%(inlaw)s",
392                 "thirty-forth great %(step)sgrandaunt%(inlaw)s",
393                 "thirty-fifth great %(step)sgrandaunt%(inlaw)s",
394                 "thirty-sixth great %(step)sgrandaunt%(inlaw)s",
395                 "thirty-seventh great %(step)sgrandaunt%(inlaw)s",
396                 "thirty-eighth great %(step)sgrandaunt%(inlaw)s",
397                 "thirty-ninth great %(step)sgrandaunt%(inlaw)s",
398                 "fourtieth great %(step)sgrandaunt%(inlaw)s",
399                 "forty-first great %(step)sgrandaunt%(inlaw)s",
400                 "forty-second great %(step)sgrandaunt%(inlaw)s",
401                 "forty-third great %(step)sgrandaunt%(inlaw)s",
402                 "forty-fourth great %(step)sgrandaunt%(inlaw)s",
403                 "forty-fifth great %(step)sgrandaunt%(inlaw)s",
404                 "forty-sixth great %(step)sgrandaunt%(inlaw)s",
405                 "forty-seventh great %(step)sgrandaunt%(inlaw)s",
406                 "forty-eighth great %(step)sgrandaunt%(inlaw)s",
407                 "forty-ninth great %(step)sgrandaunt%(inlaw)s",
408                 "fiftieth great %(step)sgrandaunt%(inlaw)s", ]
409
410_BROTHER_LEVEL = ["", "%(step)sbrother%(inlaw)s", "%(step)suncle%(inlaw)s",
411                  "%(step)sgranduncle%(inlaw)s",
412                  "great %(step)sgranduncle%(inlaw)s",
413                  "second great %(step)sgranduncle%(inlaw)s",
414                  "third great %(step)sgranduncle%(inlaw)s",
415                  "fourth great %(step)sgranduncle%(inlaw)s",
416                  "fifth great %(step)sgranduncle%(inlaw)s",
417                  "sixth great %(step)sgranduncle%(inlaw)s",
418                  "seventh great %(step)sgranduncle%(inlaw)s",
419                  "eighth great %(step)sgranduncle%(inlaw)s",
420                  "ninth great %(step)sgranduncle%(inlaw)s",
421                  "tenth great %(step)sgranduncle%(inlaw)s",
422                  "eleventh great %(step)sgranduncle%(inlaw)s",
423                  "twelfth great %(step)sgranduncle%(inlaw)s",
424                  "thirteenth great %(step)sgranduncle%(inlaw)s",
425                  "fourteenth great %(step)sgranduncle%(inlaw)s",
426                  "fifteenth great %(step)sgranduncle%(inlaw)s",
427                  "sixteenth great %(step)sgranduncle%(inlaw)s",
428                  "seventeenth great %(step)sgranduncle%(inlaw)s",
429                  "eighteenth great %(step)sgranduncle%(inlaw)s",
430                  "nineteenth great %(step)sgranduncle%(inlaw)s",
431                  "twentieth great %(step)sgranduncle%(inlaw)s",
432                  "twenty-first great %(step)sgranduncle%(inlaw)s",
433                  "twenty-second great %(step)sgranduncle%(inlaw)s",
434                  "twenty-third great %(step)sgranduncle%(inlaw)s",
435                  "twenty-fourth great %(step)sgranduncle%(inlaw)s",
436                  "twenty-fifth great %(step)sgranduncle%(inlaw)s",
437                  "twenty-sixth great %(step)sgranduncle%(inlaw)s",
438                  "twenty-seventh great %(step)sgranduncle%(inlaw)s",
439                  "twenty-eighth great %(step)sgranduncle%(inlaw)s",
440                  "twenty-ninth great %(step)sgranduncle%(inlaw)s",
441                  "thirtieth great %(step)sgranduncle%(inlaw)s",
442                  "thirty-first great %(step)sgranduncle%(inlaw)s",
443                  "thirty-second great %(step)sgranduncle%(inlaw)s",
444                  "thirty-third great %(step)sgranduncle%(inlaw)s",
445                  "thirty-fourth great %(step)sgranduncle%(inlaw)s",
446                  "thirty-fifth great %(step)sgranduncle%(inlaw)s",
447                  "thirty-sixth great %(step)sgranduncle%(inlaw)s",
448                  "thirty-seventh great %(step)sgranduncle%(inlaw)s",
449                  "thirty-eighth great %(step)sgranduncle%(inlaw)s",
450                  "thirty-ninth great %(step)sgranduncle%(inlaw)s",
451                  "fourtieth great %(step)sgranduncle%(inlaw)s",
452                  "forty-first great %(step)sgranduncle%(inlaw)s",
453                  "forty-second great %(step)sgranduncle%(inlaw)s",
454                  "forty-third great %(step)sgranduncle%(inlaw)s",
455                  "forty-fourth great %(step)sgranduncle%(inlaw)s",
456                  "forty-fifth great %(step)sgranduncle%(inlaw)s",
457                  "forty-sixth great %(step)sgranduncle%(inlaw)s",
458                  "forty-seventh great %(step)sgranduncle%(inlaw)s",
459                  "forty-eighth great %(step)sgranduncle%(inlaw)s",
460                  "forty-ninth great %(step)sgranduncle%(inlaw)s",
461                  "fiftieth great %(step)sgranduncle%(inlaw)s", ]
462
463_NEPHEW_LEVEL = ["", "%(step)snephew%(inlaw)s", "%(step)sgrandnephew%(inlaw)s",
464                 "great %(step)sgrandnephew%(inlaw)s",
465                 "second great %(step)sgrandnephew%(inlaw)s",
466                 "third great %(step)sgrandnephew%(inlaw)s",
467                 "fourth great %(step)sgrandnephew%(inlaw)s",
468                 "fifth great %(step)sgrandnephew%(inlaw)s",
469                 "sixth great %(step)sgrandnephew%(inlaw)s",
470                 "seventh great %(step)sgrandnephew%(inlaw)s",
471                 "eighth great %(step)sgrandnephew%(inlaw)s",
472                 "ninth great %(step)sgrandnephew%(inlaw)s",
473                 "tenth great %(step)sgrandnephew%(inlaw)s",
474                 "eleventh great %(step)sgrandnephew%(inlaw)s",
475                 "twelfth great %(step)sgrandnephew%(inlaw)s",
476                 "thirteenth great %(step)sgrandnephew%(inlaw)s",
477                 "fourteenth great %(step)sgrandnephew%(inlaw)s",
478                 "fifteenth great %(step)sgrandnephew%(inlaw)s",
479                 "sixteenth great %(step)sgrandnephew%(inlaw)s",
480                 "seventeenth great %(step)sgrandnephew%(inlaw)s",
481                 "eighteenth great %(step)sgrandnephew%(inlaw)s",
482                 "nineteenth great %(step)sgrandnephew%(inlaw)s",
483                 "twentieth great %(step)sgrandnephew%(inlaw)s",
484                 "twenty-first great %(step)sgrandnephew%(inlaw)s",
485                 "twenty-second great %(step)sgrandnephew%(inlaw)s",
486                 "twenty-third great %(step)sgrandnephew%(inlaw)s",
487                 "twenty-fourth great %(step)sgrandnephew%(inlaw)s",
488                 "twenty-fifth great %(step)sgrandnephew%(inlaw)s",
489                 "twenty-sixth great %(step)sgrandnephew%(inlaw)s",
490                 "twenty-seventh great %(step)sgrandnephew%(inlaw)s",
491                 "twenty-eighth great %(step)sgrandnephew%(inlaw)s",
492                 "twenty-ninth great %(step)sgrandnephew%(inlaw)s",
493                 "thirtieth great %(step)sgrandnephew%(inlaw)s",
494                 "thirty-first great %(step)sgrandnephew%(inlaw)s",
495                 "thirty-second great %(step)sgrandnephew%(inlaw)s",
496                 "thirty-third great %(step)sgrandnephew%(inlaw)s",
497                 "thirty-fourth great %(step)sgrandnephew%(inlaw)s",
498                 "thirty-fifth great %(step)sgrandnephew%(inlaw)s",
499                 "thirty-sixth great %(step)sgrandnephew%(inlaw)s",
500                 "thirty-seventh great %(step)sgrandnephew%(inlaw)s",
501                 "thirty-eighth great %(step)sgrandnephew%(inlaw)s",
502                 "thirty-ninth great %(step)sgrandnephew%(inlaw)s",
503                 "fourtieth great %(step)sgrandnephew%(inlaw)s",
504                 "forty-first great %(step)sgrandnephew%(inlaw)s",
505                 "forty-second great %(step)sgrandnephew%(inlaw)s",
506                 "forty-third great %(step)sgrandnephew%(inlaw)s",
507                 "forty-fourth great %(step)sgrandnephew%(inlaw)s",
508                 "forty-fifth great %(step)sgrandnephew%(inlaw)s",
509                 "forty-sixth great %(step)sgrandnephew%(inlaw)s",
510                 "forty-seventh great %(step)sgrandnephew%(inlaw)s",
511                 "forty-eighth great %(step)sgrandnephew%(inlaw)s",
512                 "forty-ninth great %(step)sgrandnephew%(inlaw)s",
513                 "fiftieth great %(step)sgrandnephew%(inlaw)s", ]
514
515_NIECE_LEVEL = ["", "%(step)sniece%(inlaw)s", "%(step)sgrandniece%(inlaw)s",
516                "great %(step)sgrandniece%(inlaw)s",
517                "second great %(step)sgrandniece%(inlaw)s",
518                "third great %(step)sgrandniece%(inlaw)s",
519                "fourth great %(step)sgrandniece%(inlaw)s",
520                "fifth great %(step)sgrandniece%(inlaw)s",
521                "sixth great %(step)sgrandniece%(inlaw)s",
522                "seventh great %(step)sgrandniece%(inlaw)s",
523                "eighth great %(step)sgrandniece%(inlaw)s",
524                "ninth great %(step)sgrandniece%(inlaw)s",
525                "tenth great %(step)sgrandniece%(inlaw)s",
526                "eleventh great %(step)sgrandniece%(inlaw)s",
527                "twelfth great %(step)sgrandniece%(inlaw)s",
528                "thirteenth great %(step)sgrandniece%(inlaw)s",
529                "fourteenth great %(step)sgrandniece%(inlaw)s",
530                "fifteenth great %(step)sgrandniece%(inlaw)s",
531                "sixteenth great %(step)sgrandniece%(inlaw)s",
532                "seventeenth great %(step)sgrandniece%(inlaw)s",
533                "eighteenth great %(step)sgrandniece%(inlaw)s",
534                "nineteenth great %(step)sgrandniece%(inlaw)s",
535                "twentieth great %(step)sgrandniece%(inlaw)s",
536                "twenty-first great %(step)sgrandniece%(inlaw)s",
537                "twenty-second great %(step)sgrandniece%(inlaw)s",
538                "twenty-third great %(step)sgrandniece%(inlaw)s",
539                "twenty-fourth great %(step)sgrandniece%(inlaw)s",
540                "twenty-fifth great %(step)sgrandniece%(inlaw)s",
541                "twenty-sixth great %(step)sgrandniece%(inlaw)s",
542                "twenty-seventh great %(step)sgrandniece%(inlaw)s",
543                "twenty-eighth great %(step)sgrandniece%(inlaw)s",
544                "twenty-ninth great %(step)sgrandniece%(inlaw)s",
545                "thirtieth great %(step)sgrandniece%(inlaw)s",
546                "thirty-first great %(step)sgrandniece%(inlaw)s",
547                "thirty-second great %(step)sgrandniece%(inlaw)s",
548                "thirty-third great %(step)sgrandniece%(inlaw)s",
549                "thirty-fourth great %(step)sgrandniece%(inlaw)s",
550                "thirty-fifth great %(step)sgrandniece%(inlaw)s",
551                "thirty-sixth great %(step)sgrandniece%(inlaw)s",
552                "thirty-seventh great %(step)sgrandniece%(inlaw)s",
553                "thirty-eighth great %(step)sgrandniece%(inlaw)s",
554                "thirty-ninth great %(step)sgrandniece%(inlaw)s",
555                "fourtieth great %(step)sgrandniece%(inlaw)s",
556                "forty-first great %(step)sgrandniece%(inlaw)s",
557                "forty-second great %(step)sgrandniece%(inlaw)s",
558                "forty-third great %(step)sgrandniece%(inlaw)s",
559                "forty-fourth great %(step)sgrandniece%(inlaw)s",
560                "forty-fifth great %(step)sgrandniece%(inlaw)s",
561                "forty-sixth great %(step)sgrandniece%(inlaw)s",
562                "forty-seventh great %(step)sgrandniece%(inlaw)s",
563                "forty-eighth great %(step)sgrandniece%(inlaw)s",
564                "forty-ninth great %(step)sgrandniece%(inlaw)s",
565                "fiftieth great %(step)sgrandniece%(inlaw)s", ]
566
567_CHILDREN_LEVEL = ["",
568                   "children",
569                   "grandchildren",
570                   "great grandchildren",
571                   "second great grandchildren",
572                   "third great grandchildren",
573                   "fourth great grandchildren",
574                   "fifth great grandchildren",
575                   "sixth great grandchildren",
576                   "seventh great grandchildren",
577                   "eighth great grandchildren",
578                   "ninth great grandchildren",
579                   "tenth great grandchildren",
580                   "eleventh great grandchildren",
581                   "twelfth great grandchildren",
582                   "thirteenth great grandchildren",
583                   "fourteenth great grandchildren",
584                   "fifteenth great grandchildren",
585                   "sixteenth great grandchildren",
586                   "seventeenth great grandchildren",
587                   "eighteenth great grandchildren",
588                   "nineteenth great grandchildren",
589                   "twentieth great grandchildren",
590                   "twenty-first great grandchildren",
591                   "twenty-second great grandchildren",
592                   "twenty-third great grandchildren",
593                   "twenty-fourth great grandchildren",
594                   "twenty-fifth great grandchildren",
595                   "twenty-sixth great grandchildren",
596                   "twenty-seventh great grandchildren",
597                   "twenty-eighth great grandchildren",
598                   "twenty-ninth great grandchildren",
599                   "thirtieth great grandchildren",
600                   "thirty-first great grandchildren",
601                   "thirty-second great grandchildren",
602                   "thirty-third great grandchildren",
603                   "thirty-fourth great grandchildren",
604                   "thirty-fifth great grandchildren",
605                   "thirty-sixth great grandchildren",
606                   "thirty-seventh great grandchildren",
607                   "thirty-eighth great grandchildren",
608                   "thirty-ninth great grandchildren",
609                   "fourtieth great grandchildren",
610                   "forty-first great grandchildren",
611                   "forty-second great grandchildren",
612                   "forty-third great grandchildren",
613                   "forty-fourth great grandchildren",
614                   "forty-fifth great grandchildren",
615                   "forty-sixth great grandchildren",
616                   "forty-seventh great grandchildren",
617                   "forty-eighth great grandchildren",
618                   "forty-ninth great grandchildren",
619                   "fiftieth great grandchildren", ]
620
621_SIBLINGS_LEVEL = ["",
622                   "siblings",
623                   "uncles/aunts",
624                   "granduncles/aunts",
625                   "great granduncles/aunts",
626                   "second great granduncles/aunts",
627                   "third great granduncles/aunts",
628                   "fourth great granduncles/aunts",
629                   "fifth great granduncles/aunts",
630                   "sixth great granduncles/aunts",
631                   "seventh great granduncles/aunts",
632                   "eighth great granduncles/aunts",
633                   "ninth great granduncles/aunts",
634                   "tenth great granduncles/aunts",
635                   "eleventh great granduncles/aunts",
636                   "twelfth great granduncles/aunts",
637                   "thirteenth great granduncles/aunts",
638                   "fourteenth great granduncles/aunts",
639                   "fifteenth great granduncles/aunts",
640                   "sixteenth great granduncles/aunts",
641                   "seventeenth great granduncles/aunts",
642                   "eighteenth great granduncles/aunts",
643                   "nineteenth great granduncles/aunts",
644                   "twentieth great granduncles/aunts",
645                   "twenty-first great granduncles/aunts",
646                   "twenty-second great granduncles/aunts",
647                   "twenty-third great granduncles/aunts",
648                   "twenty-fourth great granduncles/aunts",
649                   "twenty-fifth great granduncles/aunts",
650                   "twenty-sixth great granduncles/aunts",
651                   "twenty-seventh great granduncles/aunts",
652                   "twenty-eighth great granduncles/aunts",
653                   "twenty-ninth great granduncles/aunts",
654                   "thirtieth great granduncles/aunts",
655                   "thirty-first great granduncles/aunts",
656                   "thirty-second great granduncles/aunts",
657                   "thirty-third great granduncles/aunts",
658                   "thirty-fourth great granduncles/aunts",
659                   "thirty-fifth great granduncles/aunts",
660                   "thirty-sixth great granduncles/aunts",
661                   "thirty-seventh great granduncles/aunts",
662                   "thirty-eighth great granduncles/aunts",
663                   "thirty-ninth great granduncles/aunts",
664                   "fortieth great granduncles/aunts",
665                   "forty-first great granduncles/aunts",
666                   "forty-second great granduncles/aunts",
667                   "forty-third great granduncles/aunts",
668                   "forty-fourth great granduncles/aunts",
669                   "forty-fifth great granduncles/aunts",
670                   "forty-sixth great granduncles/aunts",
671                   "forty-seventh great granduncles/aunts",
672                   "forty-eighth great granduncles/aunts",
673                   "forty-ninth great granduncles/aunts",
674                   "fiftienth great granduncles/aunts", ]
675
676_SIBLING_LEVEL = ["",
677                  "%(step)ssibling%(inlaw)s",
678                  "%(step)suncle/aunt%(inlaw)s",
679                  "%(step)sgranduncle/aunt%(inlaw)s",
680                  "great %(step)sgranduncle/aunt%(inlaw)s",
681                  "second great %(step)sgranduncle/aunt%(inlaw)s",
682                  "third great %(step)sgranduncle/aunt%(inlaw)s",
683                  "fourth great %(step)sgranduncle/aunt%(inlaw)s",
684                  "fifth great %(step)sgranduncle/aunt%(inlaw)s",
685                  "sixth great %(step)sgranduncle/aunt%(inlaw)s",
686                  "seventh great %(step)sgranduncle/aunt%(inlaw)s",
687                  "eighth great %(step)sgranduncle/aunt%(inlaw)s",
688                  "ninth great %(step)sgranduncle/aunt%(inlaw)s",
689                  "tenth great %(step)sgranduncle/aunt%(inlaw)s",
690                  "eleventh great %(step)sgranduncle/aunt%(inlaw)s",
691                  "twelfth great %(step)sgranduncle/aunt%(inlaw)s",
692                  "thirteenth great %(step)sgranduncle/aunt%(inlaw)s",
693                  "fourteenth great %(step)sgranduncle/aunt%(inlaw)s",
694                  "fifteenth great %(step)sgranduncle/aunt%(inlaw)s",
695                  "sixteenth great %(step)sgranduncle/aunt%(inlaw)s",
696                  "seventeenth great %(step)sgranduncle/aunt%(inlaw)s",
697                  "eighteenth great %(step)sgranduncle/aunt%(inlaw)s",
698                  "nineteenth great %(step)sgranduncle/aunt%(inlaw)s",
699                  "twentieth great %(step)sgranduncle/aunt%(inlaw)s",
700                  "twenty-first great %(step)sgranduncle/aunt%(inlaw)s",
701                  "twenty-second great %(step)sgranduncle/aunt%(inlaw)s",
702                  "twenty-third great %(step)sgranduncle/aunt%(inlaw)s",
703                  "twenty-fourth great %(step)sgranduncle/aunt%(inlaw)s",
704                  "twenty-fifth great %(step)sgranduncle/aunt%(inlaw)s",
705                  "twenty-sixth great %(step)sgranduncle/aunt%(inlaw)s",
706                  "twenty-seventh great %(step)sgranduncle/aunt%(inlaw)s",
707                  "twenty-eighth great %(step)sgranduncle/aunt%(inlaw)s",
708                  "twenty-ninth great %(step)sgranduncle/aunt%(inlaw)s",
709                  "thirtieth great %(step)sgranduncle/aunt%(inlaw)s",
710                  "thirty-first great %(step)sgranduncle/aunt%(inlaw)s",
711                  "thirty-second great %(step)sgranduncle/aunt%(inlaw)s",
712                  "thirty-third great %(step)sgranduncle/aunt%(inlaw)s",
713                  "thirty-fourth great %(step)sgranduncle/aunt%(inlaw)s",
714                  "thirty-fifth great %(step)sgranduncle/aunt%(inlaw)s",
715                  "thirty-sixth great %(step)sgranduncle/aunt%(inlaw)s",
716                  "thirty-seventh great %(step)sgranduncle/aunt%(inlaw)s",
717                  "thirty-eighth great %(step)sgranduncle/aunt%(inlaw)s",
718                  "thirty-ninth great %(step)sgranduncle/aunt%(inlaw)s",
719                  "fortieth great %(step)sgranduncle/aunt%(inlaw)s",
720                  "forty-first great %(step)sgranduncle/aunt%(inlaw)s",
721                  "forty-second great %(step)sgranduncle/aunt%(inlaw)s",
722                  "forty-third great %(step)sgranduncle/aunt%(inlaw)s",
723                  "forty-fourth great %(step)sgranduncle/aunt%(inlaw)s",
724                  "forty-fifth great %(step)sgranduncle/aunt%(inlaw)s",
725                  "forty-sixth great %(step)sgranduncle/aunt%(inlaw)s",
726                  "forty-seventh great %(step)sgranduncle/aunt%(inlaw)s",
727                  "forty-eighth great %(step)sgranduncle/aunt%(inlaw)s",
728                  "forty-ninth great %(step)sgranduncle/aunt%(inlaw)s",
729                  "fiftieth great %(step)sgranduncle/aunt%(inlaw)s", ]
730
731_NEPHEWS_NIECES_LEVEL = ["",
732                         "siblings",
733                         "nephews/nieces",
734                         "grandnephews/nieces",
735                         "great grandnephews/nieces",
736                         "second great grandnephews/nieces",
737                         "third great grandnephews/nieces",
738                         "fourth great grandnephews/nieces",
739                         "fifth great grandnephews/nieces",
740                         "sixth great grandnephews/nieces",
741                         "seventh great grandnephews/nieces",
742                         "eighth great grandnephews/nieces",
743                         "ninth great grandnephews/nieces",
744                         "tenth great grandnephews/nieces",
745                         "eleventh great grandnephews/nieces",
746                         "twelfth great grandnephews/nieces",
747                         "thirteenth great grandnephews/nieces",
748                         "fourteenth great grandnephews/nieces",
749                         "fifteenth great grandnephews/nieces",
750                         "sixteenth great grandnephews/nieces",
751                         "seventeenth great grandnephews/nieces",
752                         "eighteenth great grandnephews/nieces",
753                         "nineteenth great grandnephews/nieces",
754                         "twentieth great grandnephews/nieces",
755                         "twenty-first great grandnephews/nieces",
756                         "twenty-second great grandnephews/nieces",
757                         "twenty-third great grandnephews/nieces",
758                         "twenty-fourth great grandnephews/nieces",
759                         "twenty-fifth great grandnephews/nieces",
760                         "twenty-sixth great grandnephews/nieces",
761                         "twenty-seventh great grandnephews/nieces",
762                         "twenty-eighth great grandnephews/nieces",
763                         "twenty-ninth great grandnephews/nieces",
764                         "thirtieth great grandnephews/nieces",
765                         "thirty-first great grandnephews/nieces",
766                         "thirty-second great grandnephews/nieces",
767                         "thirty-third great grandnephews/nieces",
768                         "thirty-fourth great grandnephews/nieces",
769                         "thirty-fifth great grandnephews/nieces",
770                         "thirty-sixth great grandnephews/nieces",
771                         "thirty-seventh great grandnephews/nieces",
772                         "thirty-eighth great grandnephews/nieces",
773                         "thirty-ninth great grandnephews/nieces",
774                         "fortieth great grandnephews/nieces",
775                         "forty-first great grandnephews/nieces",
776                         "forty-second great grandnephews/nieces",
777                         "forty-third great grandnephews/nieces",
778                         "forty-fourth great grandnephews/nieces",
779                         "forty-fifth great grandnephews/nieces",
780                         "forty-sixth great grandnephews/nieces",
781                         "forty-seventh great grandnephews/nieces",
782                         "forty-eighth great grandnephews/nieces",
783                         "forty-ninth great grandnephews/nieces",
784                         "fiftieth great grandnephews/nieces", ]
785
786
787#-------------------------------------------------------------------------
788#
789# RelationshipCalculator
790#
791#-------------------------------------------------------------------------
792class RelationshipCalculator:
793    """
794    The relationship calculator helps to determine the relationship between
795    two people.
796    """
797    REL_MOTHER = 'm'               # going up to mother
798    REL_FATHER = 'f'               # going up to father
799    REL_MOTHER_NOTBIRTH = 'M'      # going up to mother, not birth relation
800    REL_FATHER_NOTBIRTH = 'F'      # going up to father, not birth relation
801    REL_SIBLING = 's'              # going sideways to sibling (no parents)
802    REL_FAM_BIRTH = 'a'            # going up to family (mother and father)
803    REL_FAM_NONBIRTH = 'A'         # going up to family, not birth relation
804    REL_FAM_BIRTH_MOTH_ONLY = 'b'  # going up to fam, only birth rel to mother
805    REL_FAM_BIRTH_FATH_ONLY = 'c'  # going up to fam, only birth rel to father
806
807    REL_FAM_INLAW_PREFIX = 'L'     # going to the partner.
808
809    #sibling types
810    NORM_SIB = 0                   # same birth parents
811    HALF_SIB_MOTHER = 1            # same mother, father known to be different
812    HALF_SIB_FATHER = 2            # same father, mother known to be different
813    STEP_SIB = 3                   # birth parents known to be different
814    UNKNOWN_SIB = 4                # insufficient data to draw conclusion
815
816    #sibling strings
817    STEP = 'step'
818    HALF = 'half-'
819
820    INLAW = '-in-law'
821
822    #partner types
823    PARTNER_MARRIED = 1
824    PARTNER_UNMARRIED = 2
825    PARTNER_CIVIL_UNION = 3
826    PARTNER_UNKNOWN_REL = 4
827    PARTNER_EX_MARRIED = 5
828    PARTNER_EX_UNMARRIED = 6
829    PARTNER_EX_CIVIL_UNION = 7
830    PARTNER_EX_UNKNOWN_REL = 8
831
832    def __init__(self):
833        self.signal_keys = []
834        self.state_signal_key = None
835        self.storemap = False
836        self.dirtymap = True
837        self.stored_map = None
838        self.map_handle = None
839        self.map_meta = None
840        self.__db_connected = False
841        self.depth = 15
842        try:
843            from .config import config
844            self.set_depth(config.get('behavior.generation-depth'))
845        except ImportError:
846            pass
847
848        #data storage to communicate with recursive functions
849        self.__max_depth_reached = False
850        self.__loop_detected = False
851        self.__max_depth = 0
852        self.__all_families = False
853        self.__all_dist = False
854        self.__only_birth = False
855        self.__crosslinks = False
856        self.__msg = []
857
858    def set_depth(self, depth):
859        """
860        Set how deep relationships must be searched. Input must be an
861        integer > 0
862        """
863        if depth != self.depth:
864            self.depth = depth
865            self.dirtymap = True
866
867    def get_depth(self):
868        """
869        Obtain depth of relationship search
870        """
871        return self.depth
872
873    DIST_FATHER = "distant %(step)sancestor%(inlaw)s (%(level)d generations)"
874
875    def _get_father(self, level, step='', inlaw=''):
876        """
877        Internal english method to create relation string
878        """
879        if level > len(_FATHER_LEVEL) - 1:
880            return self.DIST_FATHER % {'step': step, 'inlaw': inlaw,
881                                       'level': level}
882        else:
883            return _FATHER_LEVEL[level] % {'step': step, 'inlaw': inlaw}
884
885    DIST_SON = "distant %(step)sdescendant%(inlaw)s (%(level)d generations)"
886
887    def _get_son(self, level, step='', inlaw=''):
888        """
889        Internal english method to create relation string
890        """
891        if level > len(_SON_LEVEL) - 1:
892            return self.DIST_SON % {'step': step, 'inlaw': inlaw,
893                                    'level': level}
894        else:
895            return _SON_LEVEL[level] % {'step': step, 'inlaw': inlaw}
896
897    DIST_MOTHER = "distant %(step)sancestor%(inlaw)s (%(level)d generations)"
898
899    def _get_mother(self, level, step='', inlaw=''):
900        """
901        Internal english method to create relation string
902        """
903        if level > len(_MOTHER_LEVEL) - 1:
904            return self.DIST_MOTHER % {'step': step, 'inlaw': inlaw,
905                                       'level': level}
906        else:
907            return _MOTHER_LEVEL[level] % {'step': step, 'inlaw': inlaw}
908
909    DIST_DAUGHTER = "distant %(step)sdescendant%(inlaw)s (%(level)d generations)"
910
911    def _get_daughter(self, level, step='', inlaw=''):
912        """
913        Internal english method to create relation string
914        """
915        if level > len(_DAUGHTER_LEVEL) - 1:
916            return self.DIST_DAUGHTER % {'step': step, 'inlaw': inlaw,
917                                         'level': level}
918        else:
919            return _DAUGHTER_LEVEL[level] % {'step': step, 'inlaw': inlaw}
920
921    def _get_parent_unknown(self, level, step='', inlaw=''):
922        """
923        Internal english method to create relation string
924        """
925        if level < len(_LEVEL_NAME):
926            return _LEVEL_NAME[level] + ' ' + '%sancestor%s' % (step, inlaw)
927        else:
928            return "distant %sancestor%s (%d generations)" % (step, inlaw,
929                                                              level)
930
931    DIST_CHILD = "distant %(step)sdescendant (%(level)d generations)"
932
933    def _get_child_unknown(self, level, step='', inlaw=''):
934        """
935        Internal english method to create relation string
936        """
937        if level < len(_LEVEL_NAME):
938            return _LEVEL_NAME[level] + ' ' + '%(step)sdescendant%(inlaw)s' % {
939                'step': step, 'inlaw': inlaw}
940        else:
941            return self.DIST_CHILD % {'step': step, 'level': level}
942
943    DIST_AUNT = "distant %(step)saunt%(inlaw)s"
944
945    def _get_aunt(self, level, step='', inlaw=''):
946        """
947        Internal english method to create relation string
948        """
949        if level > len(_SISTER_LEVEL) - 1:
950            return self.DIST_AUNT % {'step': step, 'inlaw': inlaw}
951        else:
952            return _SISTER_LEVEL[level] % {'step': step, 'inlaw': inlaw}
953
954    DIST_UNCLE = "distant %(step)suncle%(inlaw)s"
955
956    def _get_uncle(self, level, step='', inlaw=''):
957        """
958        Internal english method to create relation string
959        """
960        if level > len(_BROTHER_LEVEL) - 1:
961            return self.DIST_UNCLE % {'step': step, 'inlaw': inlaw}
962        else:
963            return _BROTHER_LEVEL[level] % {'step': step, 'inlaw': inlaw}
964
965    DIST_NEPHEW = "distant %(step)snephew%(inlaw)s"
966
967    def _get_nephew(self, level, step='', inlaw=''):
968        """
969        Internal english method to create relation string
970        """
971        if level > len(_NEPHEW_LEVEL) - 1:
972            return self.DIST_NEPHEW % {'step': step, 'inlaw': inlaw}
973        else:
974            return _NEPHEW_LEVEL[level] % {'step': step, 'inlaw': inlaw}
975
976    DIST_NIECE = "distant %(step)sniece%(inlaw)s"
977
978    def _get_niece(self, level, step='', inlaw=''):
979        """
980        Internal english method to create relation string
981        """
982        if level > len(_NIECE_LEVEL) - 1:
983            return self.DIST_NIECE % {'step': step, 'inlaw': inlaw}
984        else:
985            return _NIECE_LEVEL[level] % {'step': step, 'inlaw': inlaw}
986
987    def _get_cousin(self, level, removed, dir='', step='', inlaw=''):
988        """
989        Internal english method to create relation string
990        """
991        if removed == 0 and level < len(_LEVEL_NAME):
992            return "%s %scousin%s" % (_LEVEL_NAME[level], step, inlaw)
993        elif removed > len(_REMOVED_LEVEL)-1 or level > len(_LEVEL_NAME)-1:
994            return "distant %srelative%s" % (step, inlaw)
995        else:
996            return "%s %scousin%s%s%s" % (_LEVEL_NAME[level],
997                                          step, inlaw,
998                                          _REMOVED_LEVEL[removed], dir)
999
1000    DIST_SIB = "distant %(step)suncle/aunt%(inlaw)s"
1001
1002    def _get_sibling(self, level, step='', inlaw=''):
1003        """
1004        Internal english method to create relation string
1005        """
1006        if level < len(_SIBLING_LEVEL):
1007            return _SIBLING_LEVEL[level] % {'step': step, 'inlaw': inlaw}
1008        else:
1009            return self.DIST_SIB % {'step': step, 'inlaw': inlaw}
1010
1011    def get_sibling_type(self, db, orig, other):
1012        """
1013        Translation free determination of type of orig and other as siblings
1014        The procedure returns sibling types, these can be passed to
1015        get_sibling_relationship_string.
1016        Only call this method if known that orig and other are siblings
1017        """
1018        fatherorig, motherorig = self.get_birth_parents(db, orig)
1019        fatherother, motherother = self.get_birth_parents(db, other)
1020        if fatherorig and motherorig and fatherother and motherother:
1021            if fatherother == fatherorig and motherother == motherorig:
1022                return self.NORM_SIB
1023            elif fatherother == fatherorig:
1024                #all birth parents are known, one
1025                return self.HALF_SIB_FATHER
1026            elif motherother == motherorig:
1027                return self.HALF_SIB_MOTHER
1028            else:
1029                return self.STEP_SIB
1030        else:
1031            # some birth parents are not known, hence we or cannot know if
1032            # half siblings. step siblings might be possible, otherwise give up
1033            orig_nb_par = self._get_nonbirth_parent_list(db, orig)
1034            if fatherother and fatherother in orig_nb_par:
1035                #the birth parent of other is non-birth of orig
1036                if motherother and motherother == motherorig:
1037                    return self.HALF_SIB_MOTHER
1038                else:
1039                    return self.STEP_SIB
1040            if motherother and motherother in orig_nb_par:
1041                #the birth parent of other is non-birth of orig
1042                if fatherother and fatherother == fatherorig:
1043                    return self.HALF_SIB_FATHER
1044                else:
1045                    return self.STEP_SIB
1046            other_nb_par = self._get_nonbirth_parent_list(db, other)
1047            if fatherorig and fatherorig in other_nb_par:
1048                #the one birth parent of other is non-birth of orig
1049                if motherorig and motherother == motherorig:
1050                    return self.HALF_SIB_MOTHER
1051                else:
1052                    return self.STEP_SIB
1053            if motherorig and motherorig in other_nb_par:
1054                #the one birth parent of other is non-birth of orig
1055                if fatherother and fatherother == fatherorig:
1056                    return self.HALF_SIB_FATHER
1057                else:
1058                    return self.STEP_SIB
1059            #there is an unknown birth parent, it could be that this is the
1060            # birth parent of the other person
1061            return self.UNKNOWN_SIB
1062
1063    def get_birth_parents(self, db, person):
1064        """
1065        Method that returns the birthparents of a person as tuple
1066        (mother handle, father handle), if no known birthparent, the
1067        handle is replaced by None
1068        """
1069        birthfather = None
1070        birthmother = None
1071        for fam in person.get_parent_family_handle_list():
1072            family = db.get_family_from_handle(fam)
1073            if not family:
1074                continue
1075            childrel = [(ref.get_mother_relation(), ref.get_father_relation())
1076                        for ref in family.get_child_ref_list()
1077                        if ref.ref == person.handle]
1078            if not birthmother and childrel[0][0] == ChildRefType.BIRTH:
1079                birthmother = family.get_mother_handle()
1080            if not birthfather and childrel[0][1] == ChildRefType.BIRTH:
1081                birthfather = family.get_father_handle()
1082            if birthmother and birthfather:
1083                break
1084        return (birthmother, birthfather)
1085
1086    def _get_nonbirth_parent_list(self, db, person):
1087        """
1088        Returns a list of handles of parents of which it is known
1089        they are not birth parents.
1090        So all parents which do not have relation BIRTH or UNKNOWN
1091        are returned.
1092        """
1093        nb_parents = []
1094        for fam in person.get_parent_family_handle_list():
1095            family = db.get_family_from_handle(fam)
1096            if not family:
1097                continue
1098            childrel = [(ref.get_mother_relation(), ref.get_father_relation())
1099                        for ref in family.get_child_ref_list()
1100                        if ref.ref == person.handle]
1101            if childrel[0][0] != ChildRefType.BIRTH \
1102                    and childrel[0][0] != ChildRefType.UNKNOWN:
1103                nb_parents.append(family.get_mother_handle())
1104            if childrel[0][1] != ChildRefType.BIRTH \
1105                    and childrel[0][1] != ChildRefType.UNKNOWN:
1106                nb_parents.append(family.get_father_handle())
1107        #make every person appear only once:
1108        return list(set(nb_parents))
1109
1110    def _get_spouse_type(self, db, orig, other, all_rel=False):
1111        """
1112        Translation free determination if orig and other are partners.
1113        The procedure returns partner types, these can be passed to
1114        get_partner_relationship_string.
1115        If all_rel=False, returns None or a partner type.
1116        If all_rel=True, returns a list, empty if no partner
1117        """
1118        val = []
1119        for family_handle in orig.get_family_handle_list():
1120            family = db.get_family_from_handle(family_handle)
1121            # return first found spouse type
1122            if family and other.get_handle() in [family.get_father_handle(),
1123                                                 family.get_mother_handle()]:
1124                family_rel = family.get_relationship()
1125                #check for divorce event:
1126                ex = False
1127                for eventref in family.get_event_ref_list():
1128                    event = db.get_event_from_handle(eventref.ref)
1129                    if event and (event.get_type() == EventType.DIVORCE
1130                                  or event.get_type() == EventType.ANNULMENT):
1131                        ex = True
1132                        break
1133                if family_rel == FamilyRelType.MARRIED:
1134                    if ex:
1135                        val.append(self.PARTNER_EX_MARRIED)
1136                    else:
1137                        val.append(self.PARTNER_MARRIED)
1138                elif family_rel == FamilyRelType.UNMARRIED:
1139                    if ex:
1140                        val.append(self.PARTNER_EX_UNMARRIED)
1141                    else:
1142                        val.append(self.PARTNER_UNMARRIED)
1143                elif family_rel == FamilyRelType.CIVIL_UNION:
1144                    if ex:
1145                        val.append(self.PARTNER_EX_CIVIL_UNION)
1146                    else:
1147                        val.append(self.PARTNER_CIVIL_UNION)
1148                else:
1149                    if ex:
1150                        val.append(self.PARTNER_EX_UNKNOWN_REL)
1151                    else:
1152                        val.append(self.PARTNER_UNKNOWN_REL)
1153
1154        if all_rel:
1155            return val
1156        else:
1157            #last relation is normally the defenitive relation
1158            if val:
1159                return val[-1]
1160            else:
1161                return None
1162
1163    def is_spouse(self, db, orig, other, all_rel=False):
1164        """
1165        Determine the spouse relation
1166        """
1167        spouse_type = self._get_spouse_type(db, orig, other, all_rel)
1168        if spouse_type:
1169            return self.get_partner_relationship_string(spouse_type,
1170                                                        orig.get_gender(),
1171                                                        other.get_gender())
1172        else:
1173            return None
1174
1175    def get_relationship_distance_new(self, db, orig_person,
1176                                      other_person,
1177                                      all_families=False,
1178                                      all_dist=False,
1179                                      only_birth=True):
1180        """
1181        Return if all_dist == True a 'tuple, string':
1182        (rank, person handle, firstRel_str, firstRel_fam,
1183        secondRel_str, secondRel_fam), msg
1184        or if all_dist == True a 'list of tuple, string':
1185        [.....], msg:
1186
1187        .. note:: _new can be removed once all rel_xx modules no longer
1188                  overwrite get_relationship_distance
1189
1190        The tuple or list of tuples consists of:
1191
1192        ==============  =====================================================
1193        Element         Description
1194        ==============  =====================================================
1195        rank            Total number of generations from common ancestor to
1196                        the two persons, rank is -1 if no relations found
1197        person_handle   The Common ancestor
1198        firstRel_str    String with the path to the common ancestor
1199                        from orig Person
1200        firstRel_fam    Family numbers along the path as a list, eg [0,0,1].
1201                        For parent in multiple families, eg [0. [0, 2], 1]
1202        secondRel_str   String with the path to the common ancestor
1203                        from otherPerson
1204        secondRel_fam   Family numbers along the path, eg [0,0,1].
1205                        For parent in multiple families, eg [0. [0, 2], 1]
1206        msg             List of messages indicating errors. Empyt list if no
1207                        errors.
1208        ==============  =====================================================
1209
1210        Example:  firstRel_str = 'ffm' and firstRel_fam = [2,0,1] means
1211        common ancestor is mother of the second family of the father of the
1212        first family of the father of the third family.
1213
1214        Note that the same person might be present twice if the person is
1215        reached via a different branch too. Path (firstRel_str and
1216        secondRel_str) will of course be different.
1217
1218        :param db: database to work on
1219        :param orig_person: first person
1220        :type orig_person: Person Obj
1221        :param other_person: second person, relation is sought between
1222                             first and second person
1223        :type other_person:  Person Obj
1224        :param all_families: if False only Main family is searched, otherwise
1225                             all families are used
1226        :type all_families: bool
1227        :param all_dist: if False only the shortest distance is returned,
1228                         otherwise all relationships
1229        :type all_dist:  bool
1230        :param only_birth: if True only parents with birth relation are
1231                           considered
1232        :type only_birth:  bool
1233        """
1234        #data storage to communicate with recursive functions
1235        self.__max_depth_reached = False
1236        self.__loop_detected = False
1237        self.__max_depth = self.get_depth()
1238        self.__all_families = all_families
1239        self.__all_dist = all_dist
1240        self.__only_birth = only_birth
1241        self.__crosslinks = False    # no crosslinks
1242
1243        first_rel = -1
1244        second_rel = -1
1245        self.__msg = []
1246
1247        common = []
1248        first_map = {}
1249        second_map = {}
1250        rank = 9999999
1251
1252        try:
1253            if (self.storemap and self.stored_map is not None
1254                    and self.map_handle == orig_person.handle
1255                    and not self.dirtymap):
1256                first_map = self.stored_map
1257                self.__max_depth_reached, self.__loop_detected, \
1258                 self.__all_families,\
1259                 self.__all_dist, self.__only_birth,\
1260                 self.__crosslinks, self.__msg = self.map_meta
1261                self.__msg = list(self.__msg)
1262            else:
1263                self.__apply_filter(db, orig_person, '', [], first_map)
1264                self.map_meta = (self.__max_depth_reached,
1265                                 self.__loop_detected,
1266                                 self.__all_families,
1267                                 self.__all_dist, self.__only_birth,
1268                                 self.__crosslinks, list(self.__msg))
1269            self.__apply_filter(db, other_person, '', [], second_map,
1270                                stoprecursemap=first_map)
1271        except RuntimeError:
1272            return (-1, None, -1, [], -1, []), \
1273                            [_("Relationship loop detected")] + self.__msg
1274
1275        if self.storemap:
1276            self.stored_map = first_map
1277            self.dirtymap = False
1278            self.map_handle = orig_person.handle
1279
1280        for person_handle in second_map:
1281            if person_handle in first_map:
1282                com = []
1283                #a common ancestor
1284                for rel1, fam1 in zip(first_map[person_handle][0],
1285                                      first_map[person_handle][1]):
1286                    len1 = len(rel1)
1287                    for rel2, fam2 in zip(second_map[person_handle][0],
1288                                          second_map[person_handle][1]):
1289                        len2 = len(rel2)
1290                        #collect paths to arrive at common ancestor
1291                        com.append((len1+len2, person_handle, rel1, fam1,
1292                                    rel2, fam2))
1293                #insert common ancestor in correct position,
1294                #  if shorter links, check if not subset
1295                #  if longer links, check if not superset
1296                pos = 0
1297                for (ranknew, handlenew, rel1new, fam1new, rel2new,
1298                     fam2new) in com:
1299                    insert = True
1300                    for rank, handle, rel1, fam1, rel2, fam2 in common:
1301                        if ranknew < rank:
1302                            break
1303                        elif ranknew >= rank:
1304                            #check subset
1305                            if rel1 == rel1new[:len(rel1)] and \
1306                                    rel2 == rel2new[:len(rel2)]:
1307                                #subset relation exists already
1308                                insert = False
1309                                break
1310                        pos += 1
1311                    if insert:
1312                        if common:
1313                            common.insert(pos, (ranknew, handlenew, rel1new,
1314                                                fam1new, rel2new, fam2new))
1315                        else:
1316                            common = [(ranknew, handlenew, rel1new, fam1new,
1317                                       rel2new, fam2new)]
1318                        #now check if superset must be deleted from common
1319                        deletelist = []
1320                        index = pos+1
1321                        for (rank, handle, rel1, fam1, rel2,
1322                             fam2) in common[pos+1:]:
1323                            if rel1new == rel1[:len(rel1new)] and \
1324                                    rel2new == rel2[:len(rel2new)]:
1325                                deletelist.append(index)
1326                            index += 1
1327                        deletelist.reverse()
1328                        for index in deletelist:
1329                            del common[index]
1330        #check for extra messages
1331        if self.__max_depth_reached:
1332            self.__msg += [_('Family Tree reaches back more than the maximum '
1333                             '%d generations searched.\nIt is possible that '
1334                             'relationships have been missed') %
1335                           (self.__max_depth)]
1336
1337        if common and not self.__all_dist:
1338            rank = common[0][0]
1339            person_handle = common[0][1]
1340            first_rel = common[0][2]
1341            first_fam = common[0][3]
1342            second_rel = common[0][4]
1343            second_fam = common[0][5]
1344            return (rank, person_handle, first_rel, first_fam, second_rel,
1345                    second_fam), self.__msg
1346        if common:
1347            #list with tuples (rank, handle person,rel_str_orig,rel_fam_orig,
1348            #       rel_str_other,rel_fam_str) and messages
1349            return common, self.__msg
1350        if not self.__all_dist:
1351            return  (-1, None, '', [], '', []), self.__msg
1352        else:
1353            return [(-1, None, '', [], '', [])], self.__msg
1354
1355    def __apply_filter(self, db, person, rel_str, rel_fam, pmap,
1356                       depth=1, stoprecursemap=None):
1357        """
1358        Typically this method is called recursively in two ways:
1359        First method is stoprecursemap= None
1360        In this case a recursemap is builded by storing all data.
1361
1362        Second method is with a stoprecursemap given
1363        In this case parents are recursively looked up. If present in
1364        stoprecursemap, a common ancestor is found, and the method can
1365        stop looking further. If however self.__crosslinks == True, the data
1366        of first contains loops, and parents
1367        will be looked up anyway an stored if common. At end the doubles
1368        are filtered out
1369        """
1370        if person is None or not person.handle:
1371            return
1372
1373        if depth > self.__max_depth:
1374            self.__max_depth_reached = True
1375            #print('Maximum ancestor generations ('+str(depth)+') reached', \
1376            #            '(' + rel_str + ').',\
1377            #            'Stopping relation algorithm.')
1378            return
1379        depth += 1
1380
1381        commonancestor = False
1382        store = True                            #normally we store all parents
1383        if stoprecursemap:
1384            store = False                       #but not if a stop map given
1385            if person.handle in stoprecursemap:
1386                commonancestor = True
1387                store = True
1388
1389        #add person to the map, take into account that person can be obtained
1390        #from different sides
1391        if person.handle in pmap:
1392            #person is already a grandparent in another branch, we already have
1393            # had lookup of all parents, we call that a crosslink
1394            if not stoprecursemap:
1395                self.__crosslinks = True
1396            pmap[person.handle][0] += [rel_str]
1397            pmap[person.handle][1] += [rel_fam]
1398            #check if there is no loop father son of his son, ...
1399            # loop means person is twice reached, same rel_str in begin
1400            for rel1 in pmap[person.handle][0]:
1401                for rel2 in pmap[person.handle][0]:
1402                    if len(rel1) < len(rel2) and \
1403                            rel1 == rel2[:len(rel1)]:
1404                        #loop, keep one message in storage!
1405                        self.__loop_detected = True
1406                        self.__msg += [_("Relationship loop detected:") + " " +
1407                                       _("Person %(person)s connects to himself via %(relation)s")  %
1408                                       {'person' : person.get_primary_name().get_name(),
1409                                        'relation' : rel2[len(rel1):]}]
1410                        return
1411        elif store:
1412            pmap[person.handle] = [[rel_str], [rel_fam]]
1413
1414        #having added person to the pmap, we only look up recursively to
1415        # parents if this person is not common relative
1416        # if however the first map has crosslinks, we need to continue reduced
1417        if commonancestor and not self.__crosslinks:
1418            #don't continue search, great speedup!
1419            return
1420
1421        family_handles = []
1422        main = person.get_main_parents_family_handle()
1423        if main:
1424            family_handles = [main]
1425        if self.__all_families:
1426            family_handles = person.get_parent_family_handle_list()
1427
1428        try:
1429            parentstodo = {}
1430            fam = 0
1431            for family_handle in family_handles:
1432                rel_fam_new = rel_fam + [fam]
1433                family = db.get_family_from_handle(family_handle)
1434                if not family:
1435                    continue
1436                #obtain childref for this person
1437                childrel = [(ref.get_mother_relation(),
1438                             ref.get_father_relation())
1439                            for ref in family.get_child_ref_list()
1440                            if ref.ref == person.handle]
1441                fhandle = family.father_handle
1442                mhandle = family.mother_handle
1443                for data in [(fhandle, self.REL_FATHER,
1444                              self.REL_FATHER_NOTBIRTH, childrel[0][1]),
1445                             (mhandle, self.REL_MOTHER,
1446                              self.REL_MOTHER_NOTBIRTH, childrel[0][0])]:
1447                    if data[0] and data[0] not in parentstodo:
1448                        persontodo = db.get_person_from_handle(data[0])
1449                        if data[3] == ChildRefType.BIRTH:
1450                            addstr = data[1]
1451                        elif not self.__only_birth:
1452                            addstr = data[2]
1453                        else:
1454                            addstr = ''
1455                        if addstr:
1456                            parentstodo[data[0]] = (persontodo,
1457                                                    rel_str + addstr,
1458                                                    rel_fam_new)
1459                    elif data[0] and data[0] in parentstodo:
1460                        #this person is already scheduled to research
1461                        #update family list
1462                        famlist = parentstodo[data[0]][2]
1463                        if not isinstance(famlist[-1], list) and \
1464                                fam != famlist[-1]:
1465                            famlist = famlist[:-1] + [[famlist[-1]]]
1466                        if isinstance(famlist[-1], list) and \
1467                                fam not in famlist[-1]:
1468                            famlist = famlist[:-1] + [famlist[-1] + [fam]]
1469                            parentstodo[data[0]] = (parentstodo[data[0]][0],
1470                                                    parentstodo[data[0]][1],
1471                                                    famlist)
1472                if not fhandle and not mhandle and stoprecursemap is None:
1473                    #family without parents, add brothers for orig person
1474                    #other person has recusemap, and will stop when seeing
1475                    #the brother.
1476                    child_list = [ref.ref for ref in family.get_child_ref_list()
1477                                  if ref.ref != person.handle]
1478                    addstr = self.REL_SIBLING
1479                    for chandle in child_list:
1480                        if chandle in pmap:
1481                            pmap[chandle][0] += [rel_str + addstr]
1482                            pmap[chandle][1] += [rel_fam_new]
1483                            #person is already a grandparent in another branch
1484                        else:
1485                            pmap[chandle] = [[rel_str+addstr], [rel_fam_new]]
1486                fam += 1
1487
1488            for handle, data in parentstodo.items():
1489                self.__apply_filter(db, data[0],
1490                                    data[1], data[2],
1491                                    pmap, depth, stoprecursemap)
1492        except:
1493            import traceback
1494            traceback.print_exc()
1495            return
1496
1497    def collapse_relations(self, relations):
1498        """
1499        Internal method to condense the relationships as returned by
1500        get_relationship_distance_new.
1501        Common ancestors in the same family are collapsed to one entry,
1502        changing the person paths to family paths, eg 'mf' and 'mm' become 'ma'
1503
1504        relations : list of relations as returned by
1505                    get_relationship_distance_new with all_dist = True
1506
1507        returns : the same data as relations, but collapsed, hence the
1508                  handle entry is now a list of handles, and the
1509                  path to common ancestors can now contain family
1510                  identifiers (eg 'a', ...)
1511                  In the case of sibling, this is replaced by family
1512                  with common ancestor handles empty list []!
1513        """
1514        if relations[0][0] == -1:
1515            return relations
1516        commonnew = []
1517        existing_path = []
1518        for relation in relations:
1519            relstrfirst = None
1520            commonhandle = [relation[1]]
1521            if relation[2]:
1522                relstrfirst = relation[2][:-1]
1523            relstrsec = None
1524            if relation[4]:
1525                relstrsec = relation[4][:-1]
1526            relfamfirst = relation[3][:]
1527            relfamsec = relation[5][:]
1528            #handle pure sibling:
1529            rela2 = relation[2]
1530            rela4 = relation[4]
1531            if relation[2] and relation[2][-1] == self.REL_SIBLING:
1532                #sibling will be the unique common ancestor,
1533                #change to a family with unknown handle for common ancestor
1534                rela2 = relation[2][:-1] + self.REL_FAM_BIRTH
1535                rela4 = relation[4] + self.REL_FAM_BIRTH
1536                relfamsec = relfamsec + [relfamfirst[-1]]
1537                relstrsec = relation[4][:-1]
1538                commonhandle = []
1539
1540            # a unique path to family of common person:
1541            familypaths = []
1542            if relfamfirst and isinstance(relfamfirst[-1], list):
1543                if relfamsec and isinstance(relfamsec[-1], list):
1544                    for val1 in relfamfirst[-1]:
1545                        for val2 in relfamsec[-1]:
1546                            familypaths.append((relstrfirst, relstrsec,
1547                                                relfamfirst[:-1] + [val1],
1548                                                relfamsec[:-1] + [val2]))
1549                else:
1550                    for val1 in relfamfirst[-1]:
1551                        familypaths.append((relstrfirst, relstrsec,
1552                                            relfamfirst[:-1] + [val1],
1553                                            relfamsec))
1554            elif relfamsec and isinstance(relfamsec[-1], list):
1555                for val2 in relfamsec[-1]:
1556                    familypaths.append((relstrfirst, relstrsec,
1557                                        relfamfirst,
1558                                        relfamsec[:-1] + [val2]))
1559            else:
1560                familypaths.append((relstrfirst, relstrsec,
1561                                    relfamfirst, relfamsec))
1562            for familypath in familypaths:
1563                #familypath = (relstrfirst, relstrsec, relfamfirst, relfamsec)
1564                try:
1565                    posfam = existing_path.index(familypath)
1566                except ValueError:
1567                    posfam = None
1568                #if relstr is '', the ancestor is unique, if posfam None,
1569                # first time we see this family path
1570                if (posfam is not None and relstrfirst is not None and
1571                        relstrsec is not None):
1572                    # We already have a common ancestor of this family, just
1573                    # add the other, setting correct family relation.
1574                    tmp = commonnew[posfam]
1575                    frstcomstr = rela2[-1]
1576                    scndcomstr = tmp[2][-1]
1577                    newcomstra = self._famrel_from_persrel(frstcomstr,
1578                                                           scndcomstr)
1579                    frstcomstr = rela4[-1]
1580                    scndcomstr = tmp[4][-1]
1581                    newcomstrb = self._famrel_from_persrel(frstcomstr,
1582                                                           scndcomstr)
1583
1584                    commonnew[posfam] = (tmp[0], tmp[1]+commonhandle,
1585                                         rela2[:-1]+newcomstra,
1586                                         tmp[3], rela4[:-1]+newcomstrb,
1587                                         tmp[5])
1588                else:
1589                    existing_path.append(familypath)
1590                    commonnew.append((relation[0], commonhandle, rela2,
1591                                      familypath[2], rela4, familypath[3]))
1592        #we now have multiple person handles, single families, now collapse
1593        #  families again if all else equal
1594        collapsed = commonnew[:1]
1595        for rel in commonnew[1:]:
1596            found = False
1597            for newrel in collapsed:
1598                if newrel[0:3] == rel[0:3] and newrel[4] == rel[4]:
1599                    #another familypath to arrive at same result, merge
1600                    path1 = []
1601                    path2 = []
1602                    for a, b in zip(newrel[3], rel[3]):
1603                        if a == b:
1604                            path1.append(a)
1605                        elif isinstance(a, list):
1606                            path1.append(a.append(b))
1607                        else:
1608                            path1.append([a, b])
1609                    for a, b in zip(newrel[5], rel[5]):
1610                        if a == b:
1611                            path2.append(a)
1612                        elif isinstance(a, list):
1613                            path2.append(a.append(b))
1614                        else:
1615                            path2.append([a, b])
1616                    newrel[3][:] = path1[:]
1617                    newrel[5][:] = path2[:]
1618                    found = True
1619                    break
1620            if not found:
1621                collapsed.append(rel)
1622
1623        return collapsed
1624
1625    def _famrel_from_persrel(self, persrela, persrelb):
1626        """
1627        Conversion from eg 'f' and 'm' to 'a', so relation to the two
1628        persons of a common family is converted to a family relation
1629        """
1630        if persrela == persrelb:
1631            #should not happen, procedure called in error, just return value
1632            return persrela
1633        if ((persrela == self.REL_MOTHER and persrelb == self.REL_FATHER) or
1634                (persrelb == self.REL_MOTHER and persrela == self.REL_FATHER)):
1635            return self.REL_FAM_BIRTH
1636        if ((persrela == self.REL_MOTHER and
1637             persrelb == self.REL_FATHER_NOTBIRTH) or
1638                (persrelb == self.REL_MOTHER and
1639                 persrela == self.REL_FATHER_NOTBIRTH)):
1640            return self.REL_FAM_BIRTH_MOTH_ONLY
1641        if ((persrela == self.REL_FATHER and
1642             persrelb == self.REL_MOTHER_NOTBIRTH) or
1643                (persrelb == self.REL_FATHER and
1644                 persrela == self.REL_MOTHER_NOTBIRTH)):
1645            return self.REL_FAM_BIRTH_FATH_ONLY
1646        #catch calling with family relations already, return val
1647        if (persrela == self.REL_FAM_BIRTH or
1648                persrela == self.REL_FAM_BIRTH_FATH_ONLY or
1649                persrela == self.REL_FAM_BIRTH_MOTH_ONLY or
1650                persrela == self.REL_FAM_NONBIRTH):
1651            return persrela
1652        if (persrelb == self.REL_FAM_BIRTH or
1653                persrelb == self.REL_FAM_BIRTH_FATH_ONLY or
1654                persrelb == self.REL_FAM_BIRTH_MOTH_ONLY or
1655                persrelb == self.REL_FAM_NONBIRTH):
1656            return persrelb
1657        return self.REL_FAM_NONBIRTH
1658
1659    def only_birth(self, path):
1660        """
1661        Given a path to common ancestor. Return True if only birth
1662        relations, False otherwise
1663        """
1664        for value in path:
1665            if value in [self.REL_FAM_NONBIRTH, self.REL_FATHER_NOTBIRTH,
1666                         self.REL_MOTHER_NOTBIRTH]:
1667                return False
1668        return True
1669
1670    def get_one_relationship(self, db, orig_person, other_person,
1671                             extra_info=False, olocale=glocale):
1672        """
1673        Returns a string representing the most relevant relationship between
1674        the two people. If extra_info = True, extra information is returned:
1675        (relation_string, distance_common_orig, distance_common_other)
1676
1677        If olocale is passed in (a GrampsLocale) that language will be used.
1678
1679        :param olocale: allow selection of the relationship language
1680        :type olocale: a GrampsLocale instance
1681        """
1682        self._locale = olocale
1683        stop = False
1684        if orig_person is None:
1685            rel_str = _("undefined")
1686            stop = True
1687
1688        if not stop and orig_person.get_handle() == other_person.get_handle():
1689            rel_str = ''
1690            stop = True
1691
1692        if not stop:
1693            is_spouse = self.is_spouse(db, orig_person, other_person)
1694            if is_spouse:
1695                rel_str = is_spouse
1696                stop = True
1697
1698        if stop:
1699            if extra_info:
1700                return (rel_str, -1, -1)
1701            else:
1702                return rel_str
1703
1704        data, msg = self.get_relationship_distance_new(
1705            db, orig_person, other_person, all_dist=True, all_families=True,
1706            only_birth=False)
1707        if data[0][0] == -1:
1708            if extra_info:
1709                return ('', -1, -1)
1710            else:
1711                return ''
1712
1713        data = self.collapse_relations(data)
1714
1715        #most relevant relationship is a birth family relation of lowest rank
1716        databest = [data[0]]
1717        rankbest = data[0][0]
1718        for rel in data:
1719            #data is sorted on rank
1720            if rel[0] == rankbest:
1721                databest.append(rel)
1722        rel = databest[0]
1723        dist_orig = len(rel[2])
1724        dist_other = len(rel[4])
1725        if len(databest) == 1:
1726            birth = self.only_birth(rel[2]) and self.only_birth(rel[4])
1727            if dist_orig == dist_other == 1:
1728                rel_str = self.get_sibling_relationship_string(
1729                    self.get_sibling_type(db, orig_person, other_person),
1730                    orig_person.get_gender(),
1731                    other_person.get_gender())
1732            else:
1733                rel_str = self.get_single_relationship_string(
1734                    dist_orig, dist_other,
1735                    orig_person.get_gender(), other_person.get_gender(),
1736                    rel[2], rel[4], only_birth=birth,
1737                    in_law_a=False, in_law_b=False)
1738        else:
1739            order = [self.REL_FAM_BIRTH, self.REL_FAM_BIRTH_MOTH_ONLY,
1740                     self.REL_FAM_BIRTH_FATH_ONLY, self.REL_MOTHER,
1741                     self.REL_FATHER, self.REL_SIBLING, self.REL_FAM_NONBIRTH,
1742                     self.REL_MOTHER_NOTBIRTH, self.REL_FATHER_NOTBIRTH]
1743            orderbest = order.index(self.REL_MOTHER)
1744            for relother in databest:
1745                relbirth = self.only_birth(rel[2]) and self.only_birth(rel[4])
1746                if relother[2] == '' or relother[4] == '':
1747                    #direct relation, take that
1748                    rel = relother
1749                    break
1750                if not relbirth and self.only_birth(relother[2]) \
1751                                and self.only_birth(relother[4]):
1752                    #birth takes precedence
1753                    rel = relother
1754                    continue
1755                if order.index(relother[2][-1]) < order.index(rel[2][-1]) and\
1756                        order.index(relother[2][-1]) < orderbest:
1757                    rel = relother
1758                    continue
1759                if order.index(relother[4][-1]) < order.index(rel[4][-1]) and\
1760                        order.index(relother[4][-1]) < orderbest:
1761                    rel = relother
1762                    continue
1763                if order.index(rel[2][-1]) < orderbest or \
1764                        order.index(rel[4][-1]) < orderbest:
1765                    #keep the good one
1766                    continue
1767                if order.index(relother[2][-1]) < order.index(rel[2][-1]):
1768                    rel = relother
1769                    continue
1770                if order.index(relother[2][-1]) == order.index(rel[2][-1]) and\
1771                        order.index(relother[4][-1]) < order.index(rel[4][-1]):
1772                    rel = relother
1773                    continue
1774            dist_orig = len(rel[2])
1775            dist_other = len(rel[4])
1776            birth = self.only_birth(rel[2]) and self.only_birth(rel[4])
1777            if dist_orig == dist_other == 1:
1778                rel_str = self.get_sibling_relationship_string(
1779                    self.get_sibling_type(db, orig_person, other_person),
1780                    orig_person.get_gender(),
1781                    other_person.get_gender())
1782            else:
1783                rel_str = self.get_single_relationship_string(
1784                    dist_orig, dist_other,
1785                    orig_person.get_gender(), other_person.get_gender(),
1786                    rel[2], rel[4], only_birth=birth,
1787                    in_law_a=False, in_law_b=False)
1788        if extra_info:
1789            return (rel_str, dist_orig, dist_other)
1790        else:
1791            return rel_str
1792
1793    def get_all_relationships(self, db, orig_person, other_person):
1794        """
1795        Return a tuple, of which the first entry is a list with all
1796        relationships in text, and the second a list of lists of all common
1797        ancestors that have that text as relationship
1798        """
1799        relstrings = []
1800        commons = {}
1801        if orig_person is None:
1802            return ([], [])
1803
1804        if orig_person.get_handle() == other_person.get_handle():
1805            return ([], [])
1806
1807        is_spouse = self.is_spouse(db, orig_person, other_person)
1808        if is_spouse:
1809            relstrings.append(is_spouse)
1810            commons[is_spouse] = []
1811
1812        data, msg = self.get_relationship_distance_new(
1813            db, orig_person, other_person, all_dist=True, all_families=True,
1814            only_birth=False)
1815        if data[0][0] != -1:
1816            data = self.collapse_relations(data)
1817            for rel in data:
1818                rel2 = rel[2]
1819                rel4 = rel[4]
1820                rel1 = rel[1]
1821                dist_orig = len(rel[2])
1822                dist_other = len(rel[4])
1823                if rel[2] and rel[2][-1] == self.REL_SIBLING:
1824                    rel2 = rel2[:-1] + self.REL_FAM_BIRTH
1825                    dist_other += 1
1826                    rel4 = rel4 + self.REL_FAM_BIRTH
1827                    rel1 = None
1828                birth = self.only_birth(rel2) and self.only_birth(rel4)
1829                if dist_orig == dist_other == 1:
1830                    rel_str = self.get_sibling_relationship_string(
1831                        self.get_sibling_type(db, orig_person, other_person),
1832                        orig_person.get_gender(), other_person.get_gender())
1833                else:
1834                    rel_str = self.get_single_relationship_string(
1835                        dist_orig, dist_other,
1836                        orig_person.get_gender(), other_person.get_gender(),
1837                        rel2, rel4, only_birth=birth,
1838                        in_law_a=False, in_law_b=False)
1839                if rel_str not in relstrings:
1840                    relstrings.append(rel_str)
1841                    if rel1:
1842                        commons[rel_str] = rel1
1843                    else:
1844                        #unknown parent eg
1845                        commons[rel_str] = []
1846                else:
1847                    if rel1:
1848                        commons[rel_str].extend(rel1)
1849        #construct the return tupply, relstrings is ordered on rank automatic
1850        common_list = []
1851        for rel_str in relstrings:
1852            common_list.append(commons[rel_str])
1853        return (relstrings, common_list)
1854
1855    def get_plural_relationship_string(self, Ga, Gb,
1856                                       reltocommon_a='', reltocommon_b='',
1857                                       only_birth=True,
1858                                       in_law_a=False, in_law_b=False):
1859        """
1860        Provide a string that describes the relationsip between a person, and
1861        a group of people with the same relationship. E.g. "grandparents" or
1862        "children".
1863
1864        Ga and Gb can be used to mathematically calculate the relationship.
1865
1866        .. seealso::
1867            http://en.wikipedia.org/wiki/Cousin#Mathematical_definitions
1868
1869        :param Ga: The number of generations between the main person and the
1870                   common ancestor.
1871        :type Ga: int
1872        :param Gb: The number of generations between the group of people and the
1873                   common ancestor
1874        :type Gb: int
1875        :param reltocommon_a: relation path to common ancestor or common
1876                              Family for person a.
1877                              Note that length = Ga
1878        :type reltocommon_a: str
1879        :param reltocommon_b: relation path to common ancestor or common
1880                              Family for person b.
1881                              Note that length = Gb
1882        :type reltocommon_b: str
1883        :param only_birth: True if relation between a and b is by birth only
1884                           False otherwise
1885        :type only_birth: bool
1886        :param in_law_a: True if path to common ancestors is via the partner
1887                         of person a
1888        :type in_law_a: bool
1889        :param in_law_b: True if path to common ancestors is via the partner
1890                         of person b
1891        :type in_law_b: bool
1892        :returns: A string describing the relationship between the person and
1893                  the group.
1894        :rtype: str
1895        """
1896        rel_str = "distant relatives"
1897        if Ga == 0:
1898            # These are descendants
1899            if Gb < len(_CHILDREN_LEVEL):
1900                rel_str = _CHILDREN_LEVEL[Gb]
1901            else:
1902                rel_str = "distant descendants"
1903        elif Gb == 0:
1904            # These are parents/grand parents
1905            if Ga < len(_PARENTS_LEVEL):
1906                rel_str = _PARENTS_LEVEL[Ga]
1907            else:
1908                rel_str = "distant ancestors"
1909        elif Gb == 1:
1910            # These are siblings/aunts/uncles
1911            if Ga < len(_SIBLINGS_LEVEL):
1912                rel_str = _SIBLINGS_LEVEL[Ga]
1913            else:
1914                rel_str = "distant uncles/aunts"
1915        elif Ga == 1:
1916            # These are nieces/nephews
1917            if Gb < len(_NEPHEWS_NIECES_LEVEL):
1918                rel_str = _NEPHEWS_NIECES_LEVEL[Gb]
1919            else:
1920                rel_str = "distant nephews/nieces"
1921        elif Ga > 1 and Ga == Gb:
1922            # These are cousins in the same generation
1923            if Ga <= len(_LEVEL_NAME):
1924                rel_str = "%s cousins" % _LEVEL_NAME[Ga-1]
1925            else:
1926                rel_str = "distant cousins"
1927        elif Ga > 1 and Ga > Gb:
1928            # These are cousins in different generations with the second person
1929            # being in a higher generation from the common ancestor than the
1930            # first person.
1931            if Gb <= len(_LEVEL_NAME) and (Ga-Gb) < len(_REMOVED_LEVEL):
1932                rel_str = "%s cousins%s (up)" % (_LEVEL_NAME[Gb-1],
1933                                                 _REMOVED_LEVEL[Ga-Gb])
1934            else:
1935                rel_str = "distant cousins"
1936        elif Gb > 1 and Gb > Ga:
1937            # These are cousins in different generations with the second person
1938            # being in a lower generation from the common ancestor than the
1939            # first person.
1940            if Ga <= len(_LEVEL_NAME) and (Gb-Ga) < len(_REMOVED_LEVEL):
1941                rel_str = "%s cousins%s (down)" % (_LEVEL_NAME[Ga-1],
1942                                                   _REMOVED_LEVEL[Gb-Ga])
1943            else:
1944                rel_str = "distant cousins"
1945
1946        if in_law_b is True:
1947            rel_str = "spouses of %s" % rel_str
1948
1949        return rel_str
1950
1951    def get_single_relationship_string(self, Ga, Gb, gender_a, gender_b,
1952                                       reltocommon_a, reltocommon_b,
1953                                       only_birth=True,
1954                                       in_law_a=False, in_law_b=False):
1955        """
1956        Provide a string that describes the relationsip between a person, and
1957        another person. E.g. "grandparent" or "child".
1958
1959        To be used as: 'person b is the grandparent of a', this will be in
1960        translation string:  'person b is the %(relation)s of a'
1961
1962        Note that languages with gender should add 'the' inside the
1963        translation, so eg in french:  'person b est %(relation)s de a'
1964        where relation will be here: le grandparent
1965
1966        Ga and Gb can be used to mathematically calculate the relationship.
1967
1968        .. seealso::
1969            http://en.wikipedia.org/wiki/Cousin#Mathematical_definitions
1970
1971        Some languages need to know the specific path to the common ancestor.
1972        Those languages should use reltocommon_a and reltocommon_b which is
1973        a string like 'mfmf'.
1974
1975        The possible string codes are:
1976
1977        =======================  ===========================================
1978        Code                     Description
1979        =======================  ===========================================
1980        REL_MOTHER               # going up to mother
1981        REL_FATHER               # going up to father
1982        REL_MOTHER_NOTBIRTH      # going up to mother, not birth relation
1983        REL_FATHER_NOTBIRTH      # going up to father, not birth relation
1984        REL_FAM_BIRTH            # going up to family (mother and father)
1985        REL_FAM_NONBIRTH         # going up to family, not birth relation
1986        REL_FAM_BIRTH_MOTH_ONLY  # going up to fam, only birth rel to mother
1987        REL_FAM_BIRTH_FATH_ONLY  # going up to fam, only birth rel to father
1988        =======================  ===========================================
1989
1990        Prefix codes are stripped, so REL_FAM_INLAW_PREFIX is not present.
1991        If the relation starts with the inlaw of the person a, then 'in_law_a'
1992        is True, if it starts with the inlaw of person b, then 'in_law_b' is
1993        True.
1994
1995        Also REL_SIBLING (# going sideways to sibling (no parents)) is not
1996        passed to this routine. The collapse_relations changes this to a
1997        family relation.
1998
1999        Hence, calling routines should always strip REL_SIBLING and
2000        REL_FAM_INLAW_PREFIX before calling get_single_relationship_string()
2001        Note that only_birth=False, means that in the reltocommon one of the
2002        NOTBIRTH specifiers is present.
2003
2004        The REL_FAM identifiers mean that the relation is not via a common
2005        ancestor, but via a common family (note that that is not possible for
2006        direct descendants or direct ancestors!). If the relation to one of the
2007        parents in that common family is by birth, then 'only_birth' is not
2008        set to False. The only_birth() method is normally used for this.
2009
2010        :param Ga: The number of generations between the main person and the
2011                   common ancestor.
2012        :type Ga: int
2013        :param Gb: The number of generations between the other person and the
2014                   common ancestor.
2015        :type Gb: int
2016        :param gender_a: gender of person a
2017        :type gender_a: int gender
2018        :param gender_b: gender of person b
2019        :type gender_b: int gender
2020        :param reltocommon_a: relation path to common ancestor or common
2021                              Family for person a.
2022                              Note that length = Ga
2023        :type reltocommon_a: str
2024        :param reltocommon_b: relation path to common ancestor or common
2025                              Family for person b.
2026                              Note that length = Gb
2027        :type reltocommon_b: str
2028        :param in_law_a:  True if path to common ancestors is via the partner
2029                          of person a
2030        :type in_law_a: bool
2031        :param in_law_b: True if path to common ancestors is via the partner
2032                         of person b
2033        :type in_law_b: bool
2034        :param only_birth: True if relation between a and b is by birth only
2035                           False otherwise
2036        :type only_birth: bool
2037        :returns: A string describing the relationship between the two people
2038        :rtype: str
2039
2040        .. note:: 1. the self.REL_SIBLING should not be passed to this routine,
2041                     so we should not check on it. All other self.
2042                  2. for better determination of siblings, use if Ga=1=Gb
2043                     get_sibling_relationship_string
2044        """
2045        if only_birth:
2046            step = ''
2047        else:
2048            step = self.STEP
2049
2050        if in_law_a or in_law_b:
2051            inlaw = self.INLAW
2052        else:
2053            inlaw = ''
2054
2055        rel_str = "distant %srelative%s" % (step, inlaw)
2056
2057        if Ga == 0:
2058            # b is descendant of a
2059            if Gb == 0:
2060                rel_str = 'same person'
2061            elif gender_b == MALE:
2062                rel_str = self._get_son(Gb, step, inlaw)
2063            elif gender_b == FEMALE:
2064                rel_str = self._get_daughter(Gb, step, inlaw)
2065            else:
2066                rel_str = self._get_child_unknown(Gb, step, inlaw)
2067        elif Gb == 0:
2068            # b is parents/grand parent of a
2069            if gender_b == MALE:
2070                rel_str = self._get_father(Ga, step, inlaw)
2071            elif gender_b == FEMALE:
2072                rel_str = self._get_mother(Ga, step, inlaw)
2073            else:
2074                rel_str = self._get_parent_unknown(Ga, step, inlaw)
2075        elif Gb == 1:
2076            # b is sibling/aunt/uncle of a
2077            if gender_b == MALE:
2078                rel_str = self._get_uncle(Ga, step, inlaw)
2079            elif gender_b == FEMALE:
2080                rel_str = self._get_aunt(Ga, step, inlaw)
2081            else:
2082                rel_str = self._get_sibling(Ga, step, inlaw)
2083        elif Ga == 1:
2084            # b is niece/nephew of a
2085            if gender_b == MALE:
2086                rel_str = self._get_nephew(Gb-1, step, inlaw)
2087            elif gender_b == FEMALE:
2088                rel_str = self._get_niece(Gb-1, step, inlaw)
2089            elif Gb < len(_NIECE_LEVEL) and Gb < len(_NEPHEW_LEVEL):
2090                rel_str = "%s or %s" % (self._get_nephew(Gb-1, step, inlaw),
2091                                        self._get_niece(Gb-1, step, inlaw))
2092            else:
2093                rel_str = "distant %snephews/nieces%s" % (step, inlaw)
2094        elif Ga == Gb:
2095            # a and b cousins in the same generation
2096            rel_str = self._get_cousin(Ga-1, 0, dir='', step=step, inlaw=inlaw)
2097        elif Ga > Gb:
2098            # These are cousins in different generations with the second person
2099            # being in a higher generation from the common ancestor than the
2100            # first person.
2101            rel_str = self._get_cousin(Gb-1, Ga-Gb, dir=' (up)',
2102                                       step=step, inlaw=inlaw)
2103        elif Gb > Ga:
2104            # These are cousins in different generations with the second person
2105            # being in a lower generation from the common ancestor than the
2106            # first person.
2107            rel_str = self._get_cousin(Ga-1, Gb-Ga, dir=' (down)',
2108                                       step=step, inlaw=inlaw)
2109        return rel_str
2110
2111    def get_sibling_relationship_string(self, sib_type, gender_a, gender_b,
2112                                        in_law_a=False, in_law_b=False):
2113        """
2114        Determine the string giving the relation between two siblings of
2115        type sib_type.
2116        Eg: b is the brother of a
2117        Here 'brother' is the string we need to determine
2118        This method gives more details about siblings than
2119        get_single_relationship_string can do.
2120
2121        .. warning:: DON'T TRANSLATE THIS PROCEDURE IF LOGIC IS EQUAL IN YOUR
2122                     LANGUAGE, AND SAME METHODS EXIST (get_uncle, get_aunt,
2123                     get_sibling)
2124        """
2125        if sib_type == self.NORM_SIB or sib_type == self.UNKNOWN_SIB:
2126            typestr = ''
2127        elif sib_type == self.HALF_SIB_MOTHER \
2128                or sib_type == self.HALF_SIB_FATHER:
2129            typestr = self.HALF
2130        elif sib_type == self.STEP_SIB:
2131            typestr = self.STEP
2132
2133        if in_law_a or in_law_b:
2134            inlaw = self.INLAW
2135        else:
2136            inlaw = ''
2137
2138        if gender_b == MALE:
2139            rel_str = self._get_uncle(1, typestr, inlaw)
2140        elif gender_b == FEMALE:
2141            rel_str = self._get_aunt(1, typestr, inlaw)
2142        else:
2143            rel_str = self._get_sibling(1, typestr, inlaw)
2144        return rel_str
2145
2146    def get_partner_relationship_string(self, spouse_type, gender_a, gender_b):
2147        """
2148        Determine the string giving the relation between two partners of
2149        type spouse_type.
2150        Eg: b is the spouse of a
2151        Here 'spouse' is the string we need to determine
2152
2153        .. warning:: DON'T TRANSLATE THIS PROCEDURE IF LOGIC IS EQUAL IN YOUR
2154                     LANGUAGE, AS GETTEXT IS ALREADY USED !
2155        """
2156        #english only needs gender of b, we don't guess if unknown like in old
2157        # procedure as that is stupid in present day cases!
2158        gender = gender_b
2159
2160        if not spouse_type:
2161            return ''
2162
2163        trans_text = _
2164        # trans_text is a defined keyword (see po/update_po.py, po/genpot.sh)
2165        if hasattr(self, '_locale') and self._locale != glocale:
2166            trans_text = self._locale.translation.sgettext
2167
2168        if spouse_type == self.PARTNER_MARRIED:
2169            if gender == MALE:
2170                return trans_text("husband")
2171            elif gender == FEMALE:
2172                return trans_text("wife")
2173            else:
2174                return trans_text("gender unknown|spouse")
2175        elif spouse_type == self.PARTNER_EX_MARRIED:
2176            if gender == MALE:
2177                return trans_text("ex-husband")
2178            elif gender == FEMALE:
2179                return trans_text("ex-wife")
2180            else:
2181                return trans_text("gender unknown|ex-spouse")
2182        elif spouse_type == self.PARTNER_UNMARRIED:
2183            if gender == MALE:
2184                return trans_text("male,unmarried|partner")
2185            elif gender == FEMALE:
2186                return trans_text("female,unmarried|partner")
2187            else:
2188                return trans_text("gender unknown,unmarried|partner")
2189        elif spouse_type == self.PARTNER_EX_UNMARRIED:
2190            if gender == MALE:
2191                return trans_text("male,unmarried|ex-partner")
2192            elif gender == FEMALE:
2193                return trans_text("female,unmarried|ex-partner")
2194            else:
2195                return trans_text("gender unknown,unmarried|ex-partner")
2196        elif spouse_type == self.PARTNER_CIVIL_UNION:
2197            if gender == MALE:
2198                return trans_text("male,civil union|partner")
2199            elif gender == FEMALE:
2200                return trans_text("female,civil union|partner")
2201            else:
2202                return trans_text("gender unknown,civil union|partner")
2203        elif spouse_type == self.PARTNER_EX_CIVIL_UNION:
2204            if gender == MALE:
2205                return trans_text("male,civil union|former partner")
2206            elif gender == FEMALE:
2207                return trans_text("female,civil union|former partner")
2208            else:
2209                return trans_text("gender unknown,civil union|former partner")
2210        elif spouse_type == self.PARTNER_UNKNOWN_REL:
2211            if gender == MALE:
2212                return trans_text("male,unknown relation|partner")
2213            elif gender == FEMALE:
2214                return trans_text("female,unknown relation|partner")
2215            else:
2216                return trans_text("gender unknown,unknown relation|partner")
2217        else:
2218            # here we have spouse_type == self.PARTNER_EX_UNKNOWN_REL
2219            #   or other not catched types
2220            if gender == MALE:
2221                return trans_text("male,unknown relation|former partner")
2222            elif gender == FEMALE:
2223                return trans_text("female,unknown relation|former partner")
2224            else:
2225                return trans_text("gender unknown,unknown relation|former partner")
2226
2227    def connect_db_signals(self, dbstate):
2228        """
2229        We can save work by storing a map, however, if database changes
2230        this map must be regenerated.
2231        Before close, the calling app must call disconnect_db_signals
2232        """
2233        if self.__db_connected:
2234            return
2235        assert len(self.signal_keys) == 0
2236        self.state_signal_key = dbstate.connect('database-changed',
2237                                                self._dbchange_callback)
2238        self.__connect_db_signals(dbstate.db)
2239
2240    def __connect_db_signals(self, db):
2241        signals = ['person-add', 'person-update', 'person-delete',
2242                   'person-rebuild', 'family-add', 'family-update',
2243                   'family-delete', 'family-rebuild', 'database-changed']
2244        for name in signals:
2245            self.signal_keys.append(db.connect(name, self._datachange_callback))
2246        self.storemap = True
2247        self.__db_connected = True
2248
2249    def disconnect_db_signals(self, dbstate):
2250        """
2251        Method to disconnect to all signals the relationship calculator is
2252        subscribed
2253        """
2254        dbstate.disconnect(self.state_signal_key)
2255        list(map(dbstate.db.disconnect, self.signal_keys))
2256        self.storemap = False
2257        self.stored_map = None
2258
2259    def _dbchange_callback(self, db):
2260        """
2261        When database changes, the map can no longer be used.
2262        Connects must be remade
2263        """
2264        self.dirtymap = True
2265        #signals are disconnected on close of old database, connect to new
2266        self.__connect_db_signals(db)
2267
2268    def _datachange_callback(self, handle_list=None):
2269        """
2270        When data in database changes, the map can no  longer be used.
2271        As the map might be in use or might be generated at the moment,
2272        this method sets a dirty flag. Before reusing the map, this flag
2273        will be checked
2274        """
2275        self.dirtymap = True
2276
2277#-------------------------------------------------------------------------
2278#
2279# define the default relationshipcalculator
2280#
2281#-------------------------------------------------------------------------
2282
2283__RELCALC_CLASS = None
2284
2285def get_relationship_calculator(reinit=False, clocale=glocale):
2286    """
2287    Return the relationship calculator for the current language.
2288
2289    If clocale is passed in (a GrampsLocale) then that language will be used.
2290
2291    :param clocale: allow selection of the relationship language
2292    :type clocale: a GrampsLocale instance
2293
2294    """
2295    global __RELCALC_CLASS
2296
2297    if __RELCALC_CLASS is None or reinit:
2298        lang = clocale.language[0]
2299        __RELCALC_CLASS = RelationshipCalculator
2300        # If lang not set default to English relationship calulator
2301        # See if lang begins with en_, English_ or english_
2302        # If so return standard relationship calculator.
2303        if lang.startswith("en") or lang == "C":
2304            return __RELCALC_CLASS()
2305        # set correct non English relationship calculator based on lang
2306        relation_translation_found = False
2307        for plugin in PluginRegister.get_instance().relcalc_plugins():
2308            if lang in plugin.lang_list:
2309                pmgr = BasePluginManager.get_instance()
2310                # the loaded module is put in variable mod
2311                mod = pmgr.load_plugin(plugin)
2312                if mod:
2313                    __RELCALC_CLASS = getattr(mod, plugin.relcalcclass)
2314                    relation_translation_found = True
2315                    break
2316        if not relation_translation_found and \
2317            len(PluginRegister.get_instance().relcalc_plugins()):
2318            LOG.warning(_("Family relationship translator not available for "
2319                          "language '%s'. Using 'english' instead."), lang)
2320    return __RELCALC_CLASS()
2321
2322#-------------------------------------------------------------------------
2323#
2324# Tests
2325#
2326#-------------------------------------------------------------------------
2327MAX = 30
2328FMT = '%+50s'
2329
2330def _test(rcalc, onlybirth, inlawa, inlawb, printrelstr, test_num=None):
2331    """
2332    This is a generic test suite for the singular relationship
2333    TRANSLATORS: do NOT translate, use __main__ !
2334    """
2335    import sys
2336    import random
2337    random.seed()
2338    def _rand_f_m():
2339        if random.randint(0, 1) == 0:
2340            return 'f'
2341        else:
2342            return 'm'
2343
2344    def _rand_relstr(length, endstr):
2345        if length == 0:
2346            return ''
2347        else:
2348            relstr = ''
2349            for i in range(length-1):
2350                relstr += _rand_f_m()
2351            return relstr + endstr
2352
2353    if test_num is None:
2354        print("""
2355Select a test:
2356  0 - all tests
2357  1 - testing sons
2358  2 - testing daughters
2359  3 - testing unknown children
2360  4 - testing grandfathers
2361  5 - testing grandmothers
2362  6 - testing unknown parents
2363  7 - testing nieces
2364  8 - testing nephews
2365  9 - testing unknown nephews/nieces
2366  10 - testing uncles
2367  11 - testing aunts
2368  12 - testing unknown uncles/aunts
2369  13 - testing male cousins same generation
2370  14 - testing female cousins same generation
2371  15 - testing unknown cousins same generation
2372  16 - testing some cousins up
2373  17 - testing some cousins down
2374
2375Please enter a test number and press Enter for continue:
2376    """)
2377        test_num = sys.stdin.readline().strip()
2378        test_num = int(test_num)
2379
2380    if test_num == 0 or test_num == 1:
2381        print('\ntesting sons')
2382        #sys.stdin.readline()
2383        for i in range(MAX):
2384            relstr = _rand_relstr(i, 'f')
2385            rel = FMT % rcalc.get_single_relationship_string(
2386                0, i, MALE, MALE, '', relstr, only_birth=onlybirth,
2387                in_law_a=inlawa, in_law_b=inlawb)
2388            if printrelstr:
2389                print(rel + ' |info:', relstr)
2390            else:
2391                print(rel)
2392    if test_num == 0 or test_num == 2:
2393        print('\ntesting daughters\n')
2394        #sys.stdin.readline()
2395        for i in range(MAX):
2396            relstr = _rand_relstr(i, 'm')
2397            rel = FMT % rcalc.get_single_relationship_string(
2398                0, i, MALE, FEMALE, '', relstr, only_birth=onlybirth,
2399                in_law_a=inlawa, in_law_b=inlawb)
2400            if printrelstr:
2401                print(rel + ' |info:', relstr)
2402            else:
2403                print(rel)
2404    if test_num == 0 or test_num == 3:
2405        print('\ntesting unknown children\n')
2406        #sys.stdin.readline()
2407        for i in range(MAX):
2408            relstr = _rand_relstr(i, 'f')
2409            rel = FMT % rcalc.get_single_relationship_string(
2410                0, i, MALE, UNKNOWN, '', relstr, only_birth=onlybirth,
2411                in_law_a=inlawa, in_law_b=inlawb)
2412            if printrelstr:
2413                print(rel + ' |info:', relstr)
2414            else:
2415                print(rel)
2416    if test_num == 0 or test_num == 4:
2417        print('\ntesting grandfathers\n')
2418        #sys.stdin.readline()
2419        for i in range(MAX):
2420            relstr = _rand_relstr(i, 'f')
2421            rel = FMT % rcalc.get_single_relationship_string(
2422                i, 0, FEMALE, MALE, relstr, '', only_birth=onlybirth,
2423                in_law_a=inlawa, in_law_b=inlawb)
2424            if printrelstr:
2425                print(rel + ' |info:', relstr)
2426            else:
2427                print(rel)
2428    if test_num == 0 or test_num == 5:
2429        print('\ntesting grandmothers\n')
2430        #sys.stdin.readline()
2431        for i in range(MAX):
2432            relstr = _rand_relstr(i, 'm')
2433            rel = FMT % rcalc.get_single_relationship_string(
2434                i, 0, FEMALE, FEMALE, relstr, '', only_birth=onlybirth,
2435                in_law_a=inlawa, in_law_b=inlawb)
2436            if printrelstr:
2437                print(rel + ' |info:', relstr)
2438            else:
2439                print(rel)
2440    if test_num == 0 or test_num == 6:
2441        print('\ntesting unknown parents\n')
2442        #sys.stdin.readline()
2443        for i in range(MAX):
2444            relstr = _rand_relstr(i, 'f')
2445            rel = FMT % rcalc.get_single_relationship_string(
2446                i, 0, FEMALE, UNKNOWN, relstr, '', only_birth=onlybirth,
2447                in_law_a=inlawa, in_law_b=inlawb)
2448            if printrelstr:
2449                print(rel + ' |info:', relstr)
2450            else:
2451                print(rel)
2452    if test_num == 0 or test_num == 7:
2453        print('\ntesting nieces\n')
2454        #sys.stdin.readline()
2455        for i in range(1, MAX):
2456            relstr = _rand_relstr(i, 'm')
2457            rel = FMT % rcalc.get_single_relationship_string(
2458                1, i, FEMALE, FEMALE, 'm', relstr, only_birth=onlybirth,
2459                in_law_a=inlawa, in_law_b=inlawb)
2460            if printrelstr:
2461                print(rel + ' |info:', relstr)
2462            else:
2463                print(rel)
2464    if test_num == 0 or test_num == 8:
2465        print('\ntesting nephews\n')
2466        #sys.stdin.readline()
2467        for i in range(1, MAX):
2468            relstr = _rand_relstr(i, 'f')
2469            rel = FMT % rcalc.get_single_relationship_string(
2470                1, i, FEMALE, MALE, 'f', relstr, only_birth=onlybirth,
2471                in_law_a=inlawa, in_law_b=inlawb)
2472            if printrelstr:
2473                print(rel + ' |info:', relstr)
2474            else:
2475                print(rel)
2476    if test_num == 0 or test_num == 9:
2477        print('\ntesting unknown nephews/nieces\n')
2478        #sys.stdin.readline()
2479        for i in range(1, MAX):
2480            relstr = _rand_relstr(i, 'f')
2481            rel = FMT % rcalc.get_single_relationship_string(
2482                1, i, FEMALE, UNKNOWN, 'f', relstr, only_birth=onlybirth,
2483                in_law_a=inlawa, in_law_b=inlawb)
2484            if printrelstr:
2485                print(rel + ' |info:', relstr)
2486            else:
2487                print(rel)
2488    if test_num == 0 or test_num == 10:
2489        print('\ntesting uncles\n')
2490        #sys.stdin.readline()
2491        for i in range(1, MAX):
2492            relstr = _rand_relstr(i, 'f')
2493            rel = FMT % rcalc.get_single_relationship_string(
2494                i, 1, FEMALE, MALE, relstr, 'f', only_birth=onlybirth,
2495                in_law_a=inlawa, in_law_b=inlawb)
2496            if printrelstr:
2497                print(rel + ' |info:', relstr)
2498            else:
2499                print(rel)
2500    if test_num == 0 or test_num == 11:
2501        print('\ntesting aunts\n')
2502        #sys.stdin.readline()
2503        for i in range(1, MAX):
2504            relstr = _rand_relstr(i, 'f')
2505            rel = FMT % rcalc.get_single_relationship_string(
2506                i, 1, MALE, FEMALE, relstr, 'f', only_birth=onlybirth,
2507                in_law_a=inlawa, in_law_b=inlawb)
2508            if printrelstr:
2509                print(rel + ' |info:', relstr)
2510            else:
2511                print(rel)
2512    if test_num == 0 or test_num == 12:
2513        print('\ntesting unknown uncles/aunts\n')
2514        #sys.stdin.readline()
2515        for i in range(1, MAX):
2516            relstr = _rand_relstr(i, 'm')
2517            rel = FMT % rcalc.get_single_relationship_string(
2518                i, 1, MALE, UNKNOWN, relstr, 'm', only_birth=onlybirth,
2519                in_law_a=inlawa, in_law_b=inlawb)
2520            if printrelstr:
2521                print(rel + ' |info:', relstr)
2522            else:
2523                print(rel)
2524    if test_num == 0 or test_num == 13:
2525        print('\ntesting male cousins same generation\n')
2526        #sys.stdin.readline()
2527        for i in range(1, MAX):
2528            relstra = _rand_relstr(i, 'f')
2529            relstrb = _rand_relstr(i, 'f')
2530            rel = FMT % rcalc.get_single_relationship_string(
2531                i, i, MALE, MALE, relstra, relstrb, only_birth=onlybirth,
2532                in_law_a=inlawa, in_law_b=inlawb)
2533            if printrelstr:
2534                print(rel + ' |info:', relstra, relstrb)
2535            else:
2536                print(rel)
2537    if test_num == 0 or test_num == 14:
2538        print('\ntesting female cousins same generation\n')
2539        #sys.stdin.readline()
2540        for i in range(1, MAX):
2541            relstra = _rand_relstr(i, 'm')
2542            relstrb = _rand_relstr(i, 'm')
2543            rel = FMT % rcalc.get_single_relationship_string(
2544                i, i, MALE, FEMALE, relstra, relstrb, only_birth=onlybirth,
2545                in_law_a=inlawa, in_law_b=inlawb)
2546            if printrelstr:
2547                print(rel + ' |info:', relstra, relstrb)
2548            else:
2549                print(rel)
2550    if test_num == 0 or test_num == 15:
2551        print('\ntesting unknown cousins same generation\n')
2552        #sys.stdin.readline()
2553        for i in range(1, MAX):
2554            relstra = _rand_relstr(i, 'm')
2555            relstrb = _rand_relstr(i, 'm')
2556            rel = FMT % rcalc.get_single_relationship_string(
2557                i, i, MALE, UNKNOWN, relstra, relstrb, only_birth=onlybirth,
2558                in_law_a=inlawa, in_law_b=inlawb)
2559            if printrelstr:
2560                print(rel + ' |info:', relstra, relstrb)
2561            else:
2562                print(rel)
2563    if test_num == 0 or test_num == 16:
2564        print('\ntesting some cousins up\n')
2565        #sys.stdin.readline()
2566        random.seed()
2567        for i in range(1, MAX):
2568            for j in range(i, MAX):
2569                rnd = random.randint(0, 100)
2570                if rnd < 10:
2571                    relstra = _rand_relstr(j, 'f')
2572                    relstrb = _rand_relstr(i, 'f')
2573                    if rnd < 5:
2574                        rel = (FMT + ' |info: female, Ga=%2d, Gb=%2d') % (
2575                            rcalc.get_single_relationship_string(
2576                                j, i, MALE, FEMALE, relstra, relstrb,
2577                                only_birth=onlybirth,
2578                                in_law_a=inlawa, in_law_b=inlawb), j, i)
2579                        if printrelstr:
2580                            print(rel + ' |info:', relstra, relstrb)
2581                        else:
2582                            print(rel)
2583                    else:
2584                        rel = (FMT + ' |info:   male, Ga=%2d, Gb=%2d') % (
2585                            rcalc.get_single_relationship_string(
2586                                j, i, MALE, MALE, relstra, relstrb,
2587                                only_birth=onlybirth,
2588                                in_law_a=inlawa, in_law_b=inlawb), j, i)
2589                        if printrelstr:
2590                            print(rel + ' |info:', relstra, relstrb)
2591                        else:
2592                            print(rel)
2593    if test_num == 0 or test_num == 17:
2594        print('\ntesting some cousins down\n')
2595        #sys.stdin.readline()
2596        for i in range(1, MAX):
2597            for j in range(i, MAX):
2598                rnd = random.randint(0, 100)
2599                if rnd < 10:
2600                    relstra = _rand_relstr(i, 'f')
2601                    relstrb = _rand_relstr(j, 'f')
2602                    if rnd < 5:
2603                        rel = (FMT + ' |info: female, Ga=%2d, Gb=%2d') % (
2604                            rcalc.get_single_relationship_string(
2605                                i, j, MALE, FEMALE, relstra, relstrb,
2606                                only_birth=onlybirth,
2607                                in_law_a=inlawa, in_law_b=inlawb), i, j)
2608                        if printrelstr:
2609                            print(rel + ' |info:', relstra, relstrb)
2610                        else:
2611                            print(rel)
2612                    else:
2613                        rel = (FMT + ' |info:   male, Ga=%2d, Gb=%2d') % (
2614                            rcalc.get_single_relationship_string(
2615                                i, j, MALE, MALE, relstra, relstrb,
2616                                only_birth=onlybirth,
2617                                in_law_a=inlawa, in_law_b=inlawb), i, j)
2618                        if printrelstr:
2619                            print(rel + ' |info:', relstra, relstrb)
2620                        else:
2621                            print(rel)
2622
2623def _testsibling(rcalc):
2624    vals = [(rcalc.NORM_SIB, 'sibling'),
2625            (rcalc.HALF_SIB_MOTHER, 'half sib mother side'),
2626            (rcalc.HALF_SIB_FATHER, 'half sib father side'),
2627            (rcalc.STEP_SIB, 'step sib'),
2628            (rcalc.UNKNOWN_SIB, 'undetermined sib')]
2629    for gendr, strgen in [(MALE, 'male'),
2630                          (FEMALE, 'female'),
2631                          (UNKNOWN, 'unknown')]:
2632        for inlaw in [False, True]:
2633            for sibt, text in vals:
2634                print(FMT % rcalc.get_sibling_relationship_string(
2635                    sibt, MALE, gendr, in_law_a=inlaw) +
2636                      ' |info:', text, strgen)
2637
2638def _test_spouse(rcalc):
2639    vals = [(rcalc.PARTNER_MARRIED, 'married'),
2640            (rcalc.PARTNER_UNMARRIED, 'unmarried'),
2641            (rcalc.PARTNER_CIVIL_UNION, 'civil union'),
2642            (rcalc.PARTNER_UNKNOWN_REL, 'unknown rel'),
2643            (rcalc.PARTNER_EX_MARRIED, 'ex-married'),
2644            (rcalc.PARTNER_EX_UNMARRIED, 'ex-unmarried'),
2645            (rcalc.PARTNER_EX_CIVIL_UNION, 'ex civil union'),
2646            (rcalc.PARTNER_EX_UNKNOWN_REL, 'ex unknown rel')]
2647
2648    for gender, strgen in [(MALE, 'male'),
2649                           (FEMALE, 'female'),
2650                           (UNKNOWN, 'unknown')]:
2651        for spouse_type, text in vals:
2652            print(FMT % rcalc.get_partner_relationship_string(
2653                spouse_type, MALE, gender) +
2654                  ' |info: gender='+strgen+', rel='+text)
2655
2656def test(rcalc, printrelstr):
2657    """
2658    This is a generic test suite for the singular relationship
2659    TRANSLATORS: do NOT translate, call this from
2660                 __main__ in the rel_xx.py module.
2661    """
2662    import sys
2663    import argparse
2664
2665    parser = argparse.ArgumentParser(description='Test the Relationship Calculator')
2666    parser.add_argument('-r', type=int, help='type of the relations test')
2667    parser.add_argument('-s', type=int, help='type of the singular relationship test')
2668
2669    args = parser.parse_args()
2670    test_num = args.r
2671
2672    if test_num is None:
2673        print("""
2674Select a test:
2675  0 - all tests
2676  1 - Test normal relations
2677  2 - Test step relations
2678  3 - Test in-law relations (first pers)
2679  4 - Test step and in-law relations
2680  5 - Test sibling types
2681  6 - Test partner types
2682
2683Letter 'f' means Father, 'm' means Mother
2684
2685Please enter a test number and press Enter for continue:
2686    """)
2687        test_num = sys.stdin.readline().strip()
2688        test_num = int(test_num)
2689
2690    if test_num == 0 or test_num == 1:
2691        print('\n\n=== Test normal relations ===')
2692        _test(rcalc, True, False, False, printrelstr, args.s)
2693
2694    if test_num == 0 or test_num == 2:
2695        print('\n\n=== Test step relations ===')
2696        _test(rcalc, False, False, False, printrelstr, args.s)
2697
2698    if test_num == 0 or test_num == 3:
2699        print('\n\n=== Test in-law relations (first pers) ===')
2700        _test(rcalc, True, True, False, printrelstr, args.s)
2701
2702    if test_num == 0 or test_num == 4:
2703        print('\n\n=== Test step and in-law relations ===')
2704        _test(rcalc, False, True, False, printrelstr, args.s)
2705
2706    if test_num == 0 or test_num == 5:
2707        print('\n\n=== Test sibling types ===')
2708        _testsibling(rcalc)
2709
2710    if test_num == 0 or test_num == 6:
2711        print('\n\n=== Test partner types ===')
2712        _test_spouse(rcalc)
2713
2714if __name__ == "__main__":
2715    """
2716    TRANSLATORS, copy this if statement at the bottom of your
2717    rel_xx.py module, after adding: 'from Relationship import test'
2718    and test your work with:
2719    export PYTHONPATH=/path/to/gramps/src
2720    python src/plugins/rel_xx.py
2721
2722    See eg rel_fr.py at the bottom
2723    """
2724    REL_CALC = RelationshipCalculator()
2725    test(REL_CALC, True)
2726