diff --git a/src/Filters/Rules/Person/_DeepRelationshipPathBetween.py b/src/Filters/Rules/Person/_DeepRelationshipPathBetween.py new file mode 100644 index 000000000..9733dff6e --- /dev/null +++ b/src/Filters/Rules/Person/_DeepRelationshipPathBetween.py @@ -0,0 +1,155 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2009 Robert Ham +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +#------------------------------------------------------------------------- +# +# Standard Python modules +# +#------------------------------------------------------------------------- +from gettext import gettext as _ + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from gui.utils import ProgressMeter +from Filters.Rules._Rule import Rule +from Filters.Rules.Person._MatchesFilter import MatchesFilter + +#------------------------------------------------------------------------- +# +# DeepRelationshipPathBetween +# +#------------------------------------------------------------------------- + +def filter_database(db, progress, filter_name): + """Returns a list of person handles""" + + filt = MatchesFilter([filter_name]) + progress.set_header('Preparing sub-filter') + filt.prepare(db) + + progress.set_header('Retrieving all sub-filter matches') + matches = [] + for handle in db.iter_person_handles(): + person = db.get_person_from_handle(handle) + if filt.apply(db, person): + matches.append(handle) + progress.step() + + filt.reset() + + return matches + + +def get_family_handle_people(db, exclude_handle, family_handle): + people = set() + + family = db.get_family_from_handle(family_handle) + + def possibly_add_handle(h): + if h != None and h != exclude_handle: + people.add(db.get_person_from_handle(h)) + + possibly_add_handle(family.get_father_handle()) + possibly_add_handle(family.get_mother_handle()) + + for child_ref in family.get_child_ref_list(): + possibly_add_handle(child_ref.get_reference_handle()) + + return people + +def get_person_family_people(db, person, person_handle): + people = set() + + def add_family_handle_list(list): + for family_handle in list: + people.update(get_family_handle_people(db, person_handle, family_handle)) + + add_family_handle_list(person.get_family_handle_list()) + add_family_handle_list(person.get_parent_family_handle_list()) + + return people + +def find_deep_relations(db, progress, person, path, seen, target_people): + if len(target_people) < 1: + return [] + + progress.set_header(_('Evaluating ') + person.get_primary_name().get_name()) + + handle = person.get_handle() + if handle in seen: + return [] + seen.append(handle) + + return_paths = [] + person_path = path + [handle] + + if handle in target_people: + return_paths += [person_path] + target_people.remove(handle) + + family_people = get_person_family_people(db, person, handle) + for family_person in family_people: + return_paths += find_deep_relations(db, progress, family_person, person_path, seen, target_people) + progress.step() + + return return_paths + +class DeepRelationshipPathBetween(Rule): + """Checks if there is any familial connection between a person and a + filter match by searching over all connections.""" + + labels = [ _('ID:'), _('Filter name:') ] + name = _("Relationship path between and people matching ") + category = _('Relationship filters') + description = _("Searches over the database starting from a specified person and" + " returns everyone between that person and a set of target people specified" + " with a filter. This produces a set of relationship paths (including" + " by marriage) between the specified person and the target people." + " Each path is not necessarily the shortest path.") + + def prepare(self, db): + root_person_id = self.list[0] + root_person = db.get_person_from_gramps_id(root_person_id) + + progress = ProgressMeter(_('Finding relationship paths')) + progress.set_pass(header=_('Evaluating people'), mode=ProgressMeter.MODE_ACTIVITY) + + filter_name = self.list[1] + target_people = filter_database(db, progress, filter_name) + + paths = find_deep_relations(db, progress, root_person, [], [], target_people) + + progress.close() + progress = None + + self.__matches = set() + for path in paths: + self.__matches.update(path) + + def reset(self): + self.__matches = set() + + def apply(self, db, person): + return person.get_handle() in self.__matches diff --git a/src/Filters/Rules/Person/_HasTextMatchingRegexpOf.py b/src/Filters/Rules/Person/_HasTextMatchingRegexpOf.py index 9302f2121..2a89f6a5a 100644 --- a/src/Filters/Rules/Person/_HasTextMatchingRegexpOf.py +++ b/src/Filters/Rules/Person/_HasTextMatchingRegexpOf.py @@ -46,13 +46,13 @@ class HasTextMatchingRegexpOf(HasTextMatchingSubstringOf): def prepare(self,db): self.db = db - self.person_map = {} - self.event_map = {} - self.source_map = {} - self.repo_map = {} - self.family_map = {} - self.place_map = {} - self.media_map = {} + self.person_map = set() + self.event_map = set() + self.source_map = set() + self.repo_map = set() + self.family_map = set() + self.place_map = set() + self.media_map = set() self.case_sensitive = False self.regexp_match = True self.cache_sources() diff --git a/src/Filters/Rules/Person/_IsAncestorOfFilterMatch.py b/src/Filters/Rules/Person/_IsAncestorOfFilterMatch.py index 7ce86efd8..f5eb369b0 100644 --- a/src/Filters/Rules/Person/_IsAncestorOfFilterMatch.py +++ b/src/Filters/Rules/Person/_IsAncestorOfFilterMatch.py @@ -56,7 +56,7 @@ class IsAncestorOfFilterMatch(IsAncestorOf,MatchesFilter): def prepare(self,db): self.db = db - self.map = {} + self.map = set() try: if int(self.list[1]): first = 0 @@ -73,7 +73,7 @@ class IsAncestorOfFilterMatch(IsAncestorOf,MatchesFilter): filt.reset() def reset(self): - self.map = {} + self.map.clear() def apply(self,db,person): return person.handle in self.map diff --git a/src/Filters/Rules/Person/_IsDescendantFamilyOf.py b/src/Filters/Rules/Person/_IsDescendantFamilyOf.py index 815dd1d55..b61f09acd 100644 --- a/src/Filters/Rules/Person/_IsDescendantFamilyOf.py +++ b/src/Filters/Rules/Person/_IsDescendantFamilyOf.py @@ -97,6 +97,7 @@ class IsDescendantFamilyOf(Rule): def exclude(self): # This removes root person and his/her spouses from the matches set + if not self.root_person: return self.matches.remove(self.root_person.handle) for family_handle in self.root_person.get_family_handle_list(): family = self.db.get_family_from_handle(family_handle) diff --git a/src/Filters/Rules/Person/_IsDescendantOfFilterMatch.py b/src/Filters/Rules/Person/_IsDescendantOfFilterMatch.py index 26beef83b..8b9925c97 100644 --- a/src/Filters/Rules/Person/_IsDescendantOfFilterMatch.py +++ b/src/Filters/Rules/Person/_IsDescendantOfFilterMatch.py @@ -56,7 +56,7 @@ class IsDescendantOfFilterMatch(IsDescendantOf,MatchesFilter): def prepare(self,db): self.db = db - self.map = {} + self.map = set() try: if int(self.list[1]): first = 0 @@ -73,7 +73,7 @@ class IsDescendantOfFilterMatch(IsDescendantOf,MatchesFilter): filt.reset() def reset(self): - self.map = {} + self.map.clear() def apply(self,db,person): return person.handle in self.map diff --git a/src/Filters/Rules/Person/_IsMoreThanNthGenerationDescendantOf.py b/src/Filters/Rules/Person/_IsMoreThanNthGenerationDescendantOf.py index e0074e536..47ba26309 100644 --- a/src/Filters/Rules/Person/_IsMoreThanNthGenerationDescendantOf.py +++ b/src/Filters/Rules/Person/_IsMoreThanNthGenerationDescendantOf.py @@ -60,7 +60,7 @@ class IsMoreThanNthGenerationDescendantOf(Rule): pass def reset(self): - self.map = {} + self.map.reset() def apply(self,db,person): return person.handle in self.map diff --git a/src/Filters/Rules/Person/__init__.py b/src/Filters/Rules/Person/__init__.py index e492240e2..92a612deb 100644 --- a/src/Filters/Rules/Person/__init__.py +++ b/src/Filters/Rules/Person/__init__.py @@ -92,6 +92,7 @@ from _PeoplePrivate import PeoplePrivate from _PersonWithIncompleteEvent import PersonWithIncompleteEvent from _ProbablyAlive import ProbablyAlive from _RelationshipPathBetween import RelationshipPathBetween +from _DeepRelationshipPathBetween import DeepRelationshipPathBetween from _RelationshipPathBetweenBookmarks import RelationshipPathBetweenBookmarks from Filters.Rules._Rule import Rule from _SearchName import SearchName @@ -161,6 +162,7 @@ editor_rule_list = [ IsSpouseOfFilterMatch, IsSiblingOfFilterMatch, RelationshipPathBetween, + DeepRelationshipPathBetween, RelationshipPathBetweenBookmarks, HasTextMatchingSubstringOf, HasNote, diff --git a/src/Filters/_FilterParser.py b/src/Filters/_FilterParser.py index c2e2ec017..c8a4c4963 100644 --- a/src/Filters/_FilterParser.py +++ b/src/Filters/_FilterParser.py @@ -182,6 +182,8 @@ old_names_2_class = { "Is a sibling of filter match" : Rules.Person.IsSiblingOfFilterMatch, "Relationship path between two people" : Rules.Person.RelationshipPathBetween, + "Relationship paths between a person and a list of people" : + Rules.Person.DeepRelationshipPathBetween, "People who were adopted" : Rules.Person.HaveAltFamilies, "People who have images" : Rules.Person.HavePhotos, "People with children" : Rules.Person.HaveChildren,