From 77124496e831aa036c77383f350a92f8b9d8c710 Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Sun, 3 Feb 2019 18:33:46 +0100 Subject: [PATCH] New tool to suppress leading and trailing spaces. This tool is looking for place names with leading or/and trailing spaces. It also looks for in the first name and surname. For each entry which contains leading or trailing spaces, a row is added in a treeview. You can see where the spaces are for each row as the name is underlined. If you double click on the row, you can edit the Place or the Person. Fixes #10824 --- gramps/plugins/tool/removespaces.glade | 235 ++++++++++++++++++ gramps/plugins/tool/removespaces.py | 315 +++++++++++++++++++++++++ gramps/plugins/tool/tools.gpr.py | 25 ++ 3 files changed, 575 insertions(+) create mode 100644 gramps/plugins/tool/removespaces.glade create mode 100644 gramps/plugins/tool/removespaces.py diff --git a/gramps/plugins/tool/removespaces.glade b/gramps/plugins/tool/removespaces.glade new file mode 100644 index 000000000..05378ffb2 --- /dev/null +++ b/gramps/plugins/tool/removespaces.glade @@ -0,0 +1,235 @@ + + + + + + False + 650 + 400 + dialog + + + + + + + True + False + vertical + + + True + False + end + + + gtk-help + True + True + True + False + True + True + + + + False + False + 1 + + + + + gtk-close + True + True + True + False + True + right + True + + + + False + False + end + 1 + + + + + False + True + end + 0 + + + + + True + False + 6 + vertical + 6 + + + 1 + True + False + False + center + start + + + False + False + 5 + 0 + + + + + True + False + 2 + 2 + vertical + 5 + True + top + + + True + False + vertical + + + True + False + vertical + + + True + False + label + + + False + True + 0 + + + + + False + True + 0 + + + + + True + True + in + + + True + True + + + + + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + vertical + + + True + False + vertical + + + True + False + label + + + False + True + 0 + + + + + False + True + 0 + + + + + True + True + in + + + True + True + + + + + + + + True + True + 1 + + + + + False + True + 1 + + + + + True + True + 1 + + + + + True + True + 1 + + + + + + close + + + diff --git a/gramps/plugins/tool/removespaces.py b/gramps/plugins/tool/removespaces.py new file mode 100644 index 000000000..952a94fe0 --- /dev/null +++ b/gramps/plugins/tool/removespaces.py @@ -0,0 +1,315 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2019- Serge Noiraud +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +"Find possible leading and/or trailing spaces in places name and people" + +#------------------------------------------------------------------------ +# +# GNOME/GTK modules +# +#------------------------------------------------------------------------ +from gi.repository import Gtk +from gi.repository import GObject + +#------------------------------------------------------------------------ +# +# Gramps modules +# +#------------------------------------------------------------------------ +from gramps.gen.const import URL_MANUAL_PAGE +from gramps.gui.plug import tool +from gramps.gui.editors import (EditPlace, EditPerson) +from gramps.gen.errors import WindowActiveError +from gramps.gui.managedwindow import ManagedWindow +from gramps.gui.utils import ProgressMeter +from gramps.gui.display import display_help +from gramps.gui.glade import Glade +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Tools' % URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Remove_leading_and_trailing_spaces') + +#------------------------------------------------------------------------ +# +# RemoveSpaces class +# +#------------------------------------------------------------------------ +class RemoveSpaces(ManagedWindow): + """ + Find leading and trailing spaces in Place names and person names + """ + def __init__(self, dbstate, user, options_class, name, callback=None): + uistate = user.uistate + dummy_opt = options_class + dummy_nme = name + dummy_cb = callback + + self.title = _('Clean input data') + ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + self.uistate = uistate + self.db = dbstate.db + + top_dialog = Glade() + + top_dialog.connect_signals({ + "destroy_passed_object" : self.close, + "on_help_clicked" : self.on_help_clicked, + "on_delete_event" : self.close, + }) + + window = top_dialog.toplevel + title = top_dialog.get_object("title") + self.set_window(window, title, self.title) + tip = _('Search leading and/or trailing spaces for persons' + ' and places. Search comma in coordinates fields.\n' + 'Double click on a row to edit its content.') + title.set_tooltip_text(tip) + + # start the progress indicator + self.progress = ProgressMeter(self.title, _('Starting'), + parent=uistate.window) + steps = self.db.get_number_of_people() + self.db.get_number_of_places() + self.progress.set_pass(_('Looking for possible fields with leading or' + ' trailing spaces'), steps) + + self.model_1 = Gtk.ListStore( + GObject.TYPE_STRING, # 0==handle + GObject.TYPE_STRING, # 1==firstname + GObject.TYPE_STRING, # 2==surname + GObject.TYPE_STRING, # 3==alternate name + GObject.TYPE_STRING, # 4==group_as + ) + self.model_1.set_sort_column_id( + Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 1) + + self.label_1 = top_dialog.get_object("label_1") + self.label_1.set_text(_('Person')) + self.treeview_1 = top_dialog.get_object("treeview_1") + self.treeview_1.set_model(self.model_1) + col1 = Gtk.TreeViewColumn(_('handle'), + Gtk.CellRendererText(), text=0) + renderer1 = Gtk.CellRendererText() + renderer1.set_property('underline-set', True) + renderer1.set_property('underline', 2) # 2=double underline + col2 = Gtk.TreeViewColumn(_('firstname'), renderer1, text=1) + renderer2 = Gtk.CellRendererText() + renderer2.set_property('underline-set', True) + renderer2.set_property('underline', 2) # 2=double underline + col3 = Gtk.TreeViewColumn(_('surname'), renderer2, text=2) + renderer3 = Gtk.CellRendererText() + renderer3.set_property('underline-set', True) + renderer3.set_property('underline', 2) # 2=double underline + col4 = Gtk.TreeViewColumn(_('alternate name'), renderer3, text=3) + renderer4 = Gtk.CellRendererText() + renderer4.set_property('underline-set', True) + renderer4.set_property('underline', 2) # 2=double underline + col5 = Gtk.TreeViewColumn(_('group as'), renderer4, text=4) + col1.set_resizable(True) + col1.set_visible(False) + col2.set_resizable(True) + col3.set_resizable(True) + col4.set_resizable(True) + col5.set_resizable(True) + col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col3.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col4.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col5.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + self.treeview_1.append_column(col1) + self.treeview_1.append_column(col2) + self.treeview_1.append_column(col3) + self.treeview_1.append_column(col4) + self.treeview_1.append_column(col5) + self.treeselection = self.treeview_1.get_selection() + self.treeview_1.connect('row-activated', self.rowactivated_cb1) + + self.model_2 = Gtk.ListStore( + GObject.TYPE_STRING, # 0==handle + GObject.TYPE_STRING, # 1==name + GObject.TYPE_STRING, # 2==latitude + GObject.TYPE_STRING) # 3==longitude + self.model_2.set_sort_column_id( + Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 1) + + self.label_2 = top_dialog.get_object("label_2") + self.label_2.set_text(_('Place')) + self.treeview_2 = top_dialog.get_object("treeview_2") + self.treeview_2.set_model(self.model_2) + col1 = Gtk.TreeViewColumn(_('handle'), + Gtk.CellRendererText(), text=0) + renderer5 = Gtk.CellRendererText() + renderer5.set_property('underline-set', True) + renderer5.set_property('underline', 2) # 2=double underline + col2 = Gtk.TreeViewColumn(_('name'), renderer5, text=1) + renderer6 = Gtk.CellRendererText() + renderer6.set_property('underline-set', True) + renderer6.set_property('underline', 2) # 2=double underline + col3 = Gtk.TreeViewColumn(_('latitude'), renderer6, text=2) + renderer7 = Gtk.CellRendererText() + renderer7.set_property('underline-set', True) + renderer7.set_property('underline', 2) # 2=double underline + col4 = Gtk.TreeViewColumn(_('longitude'), renderer7, text=3) + col1.set_resizable(True) + col1.set_visible(False) + col2.set_resizable(True) + col3.set_resizable(True) + col4.set_resizable(True) + col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col3.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col4.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + self.treeview_2.append_column(col1) + self.treeview_2.append_column(col2) + self.treeview_2.append_column(col3) + self.treeview_2.append_column(col4) + self.treeselection = self.treeview_2.get_selection() + self.treeview_2.connect('row-activated', self.rowactivated_cb2) + + self.places() + self.people() + + # close the progress bar + self.progress.close() + + self.show() + + def places(self): + """ + For all places in the database, if the name contains leading or + trailing spaces. + """ + for place_handle in self.db.get_place_handles(): + self.progress.step() + place = self.db.get_place_from_handle(place_handle) + place_name = place.get_name() + pname = place_name.get_value() + found = False + if pname != pname.strip(): + found = True + plat = place.get_latitude() + if plat != plat.strip(): + found = True + if plat.find(',') != -1: + found = True + plon = place.get_longitude() + if plon != plon.strip(): + found = True + if plon.find(',') != -1: + found = True + if found: + value = (place_handle, pname, plat, plon) + self.model_2.append(value) + return True + + def people(self): + """ + For all persons in the database, if the name contains leading or + trailing spaces. Works for alternate names and group_as. + """ + for person_handle in self.db.get_person_handles(): + self.progress.step() + person = self.db.get_person_from_handle(person_handle) + primary_name = person.get_primary_name() + fname = primary_name.get_first_name() + found = False + if fname != fname.strip(): + found = True + sname = primary_name.get_primary_surname().get_surname() + if sname != sname.strip(): + found = True + paname = "" + for name in primary_name.get_surname_list(): + aname = name.get_surname() + if aname != sname and aname != aname.strip(): + found = True + if paname != "": + paname += ', ' + paname += aname + groupas = primary_name.group_as + if groupas != groupas.strip(): + found = True + if found: + value = (person_handle, fname, sname, paname, groupas) + self.model_1.append(value) + return True + + def rowactivated_cb1(self, treeview, path, column): + """ + Called when a Person row is activated. + """ + dummy_tv = treeview + dummy_col = column + iter_ = self.model_1.get_iter(path) + handle = self.model_1.get_value(iter_, 0) + person = self.dbstate.db.get_person_from_handle(handle) + if person: + try: + EditPerson(self.dbstate, self.uistate, [], person) + except WindowActiveError: + pass + return True + return False + + def rowactivated_cb2(self, treeview, path, column): + """ + Called when a Place row is activated. + """ + dummy_tv = treeview + dummy_col = column + iter_ = self.model_2.get_iter(path) + handle = self.model_2.get_value(iter_, 0) + place = self.dbstate.db.get_place_from_handle(handle) + if place: + try: + EditPlace(self.dbstate, self.uistate, [], place) + except WindowActiveError: + pass + return True + return False + + def on_help_clicked(self, _obj): + """ + Display the relevant portion of Gramps manual. + """ + display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) + + def close(self, *obj): + ManagedWindow.close(self, *obj) + +#------------------------------------------------------------------------ +# +# RemoveSpacesOptions +# +#------------------------------------------------------------------------ +class RemoveSpacesOptions(tool.ToolOptions): + """ + Defines options and provides handling interface. + """ + def __init__(self, name, person_id=None): + """ Initialize the options class """ + tool.ToolOptions.__init__(self, name, person_id) diff --git a/gramps/plugins/tool/tools.gpr.py b/gramps/plugins/tool/tools.gpr.py index ec3ef498c..fdd362904 100644 --- a/gramps/plugins/tool/tools.gpr.py +++ b/gramps/plugins/tool/tools.gpr.py @@ -476,3 +476,28 @@ toolclass = 'FindLoop', optionclass = 'FindLoopOptions', tool_modes = [TOOL_MODE_GUI] ) + +#------------------------------------------------------------------------ +# +# Remove leading and trailing spaces for places name +# Remove leading and trailing spaces for surname and first names +# +#------------------------------------------------------------------------ + +register(TOOL, +id = 'removespaces', +name = _("Clean input data"), +description = _("Searches the entire database, looking for " + "trailing or leading spaces for places and people." + " Search comma in coordinates fields in places."), +version = '1.0', +gramps_target_version = MODULE_VERSION, +status = STABLE, +fname = 'removespaces.py', +authors = ["Serge Noiraud"], +authors_email = ["serge.noiraud@free.fr"], +category = TOOL_UTILS, +toolclass = 'RemoveSpaces', +optionclass = 'RemoveSpacesOptions', +tool_modes = [TOOL_MODE_GUI] + )