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
This commit is contained in:
SNoiraud 2019-02-03 18:33:46 +01:00 committed by Nick Hall
parent 6bca4083cb
commit 77124496e8
3 changed files with 575 additions and 0 deletions

View File

@ -0,0 +1,235 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.10"/>
<object class="GtkDialog" id="removespaces">
<property name="can_focus">False</property>
<property name="default_width">650</property>
<property name="default_height">400</property>
<property name="type_hint">dialog</property>
<signal name="delete-event" handler="on_delete_event" swapped="no"/>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="helpbutton1">
<property name="label">gtk-help</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_help_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="close">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<property name="image_position">right</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="destroy_passed_object" swapped="yes"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="title">
<property name="height_request">1</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="double_buffered">False</property>
<property name="justify">center</property>
<property name="ellipsize">start</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<property name="homogeneous">True</property>
<property name="baseline_position">top</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label_1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="treeview_1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection1"/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label_2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="treeview_2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection2"/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="0">close</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -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)

View File

@ -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]
)