gramps/gramps/plugins/tool/reorderids.py
Paul Culley f3b5f75e37 Fix strings containing deprecated illegal escape sequences (#648)
Python 3.6 and above has deprecated illegal string escape sequences. Escape sequences are preceded by a '\' and valid ones are "\n\t\r" etc. Illegal ones are not in the list https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals.

Previous to Python 3.6 these illegal sequences were ignored and the '\' was left in place. Pylint has been noting these for a while now.

This PR corrects these sequences in Gramps. I used

find . -name "*.py" | xargs -t -n 1 python3 -Wd -m py_compile 2>&1 | grep Depre

to locate the failing strings.
2018-09-04 12:03:17 +10:00

702 lines
28 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
# Copyright (C) 2008 Brian G. Matherly
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2017 Alois Poettker <alois.poettker@gmx.de>
#
# 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.
#
"""
Change IDs of all elements in the database to conform to the
scheme specified in the database's prefix ids
"""
#------------------------------------------------------------------------
#
# standard python modules
#
#------------------------------------------------------------------------
import re
from gi.repository import Gtk, Gdk
#------------------------------------------------------------------------
#
# Gramps modules
#
#------------------------------------------------------------------------
from gramps.gen.const import URL_MANUAL_PAGE
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.sgettext
from gramps.gen.config import config
from gramps.gen.db import DbTxn
from gramps.gen.updatecallback import UpdateCallback
from gramps.gui.display import display_help
from gramps.gui.glade import Glade
from gramps.gui.managedwindow import ManagedWindow
from gramps.gui.plug import tool
from gramps.gui.utils import ProgressMeter
from gramps.gui.widgets import MonitoredCheckbox, MonitoredEntry
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
WIKI_HELP_PAGE = '%s_-_Tools' % URL_MANUAL_PAGE
WIKI_HELP_SEC = _('manual|Reorder_Gramps_ID')
PREFIXES = {'person': 'i', 'family': 'f', 'event': 'e', 'place': 'p',
'source': 's', 'citation': 'c', 'repository': 'r',
'media': 'o', 'note': 'n'}
#-------------------------------------------------------------------------
#
# Actual tool
#
#-------------------------------------------------------------------------
# gets the prefix, number, suffix specified in a format string, eg:
# P%04dX returns 'P', '04', 'X' It has to have the integer format with at
# least 3 digits to pass.
_parseformat = re.compile(r'(^[^\d]*)%(0[3-9])d([^\d]*$)')
class ReorderEntry(object):
""" Class for internal values for every primary object """
def __init__(self, object_fmt, quant_id, nextgramps_id, obj):
self.object = obj
self.width_fmt = 4
self.object_fmt, self.object_prefix, self.object_suffix = '', '', ''
self.quant_id, self.actual_id, self.actgramps_id = 0, 0, '0'
self.calc_id(object_fmt, quant_id)
self.stored_fmt = self.object_fmt
self.stored_prefix = self.object_prefix
self.stored_suffix = self.object_suffix
self.number_id = int(nextgramps_id[len(self.object_prefix):
(len(nextgramps_id) -
len(self.object_suffix))])
self.step_id = 1
self.active_obj, self.change_obj, self.keep_obj = True, False, False
def set_active(self, active):
""" sets Change flag """
self.active_obj = active
def get_active(self):
""" gets Change flag """
return self.active_obj
def set_fmt(self, object_fmt):
""" sets primary object format """
if object_fmt:
self.calc_id(object_fmt.strip(), self.quant_id)
def get_fmt(self):
""" gets primary object format """
return self.object_fmt
def res_fmt(self):
""" restore primary object format """
return self.stored_fmt
def set_change(self, change):
""" sets Change flag """
self.change_obj = change
def get_change(self):
""" gets Change flag """
return self.change_obj
def __ret_gid(self, actual):
""" return Gramps ID in correct format """
return '%s%s%s' % \
(self.object_prefix, str(actual).zfill(self.width_fmt),
self.object_suffix)
def calc_id(self, object_fmt, quant_id):
""" calculates identifier prefix, suffix, format & actual value.
Requires a valid format or returns the default instead """
self.object_fmt, self.quant_id = object_fmt, quant_id
# Default values, ID counting starts with zero!
formatmatch = _parseformat.match(object_fmt)
if formatmatch:
self.object_prefix = formatmatch.groups()[0]
self.width_fmt = int(formatmatch.groups()[1])
self.object_suffix = formatmatch.groups()[2]
else: # not a legal format string, use default
self.object_prefix = PREFIXES[self.object].upper()
self.width_fmt = 4
self.object_suffix = ''
self.object_fmt = PREFIXES[self.object].upper() + "%04d"
self.actgramps_id = self.__ret_gid(self.actual_id)
def zero_id(self):
""" provide zero Start ID """
return self.__ret_gid(0)
def set_id(self, actual):
""" sets Start ID """
text = ''.join([i for i in actual.strip() if i in '0123456789'])
self.actual_id = int(text) if text else 0
def get_id(self):
""" gets Start ID """
return self.__ret_gid(self.actual_id)
def next_id(self):
""" provide next Start ID """
return self.__ret_gid(self.number_id)
def succ_id(self):
""" provide next actual Gramps ID """
self.actual_id += self.step_id
self.actgramps_id = self.__ret_gid(self.actual_id)
return self.actgramps_id
def last_id(self):
""" provide quantities of Gramps IDs """
if self.quant_id > 0:
return self.__ret_gid(self.quant_id - 1)
else:
return self.__ret_gid(0)
def set_step(self, step):
""" sets ID Step width """
text = ''.join([i for i in step.strip() if i in '0123456789'])
self.step_id = int(text) if text else 1
def get_step(self):
""" gets ID Step width """
return str(self.step_id)
def change_step(self, step_entry):
""" change Glade Step entry """
step_id = step_entry.get_text().strip()
if step_id and step_id != str(self.step_id):
step_entry.set_text(str(self.step_id))
def set_keep(self, keep):
""" sets Keep flag """
self.keep_obj = keep
def get_keep(self):
""" gets Keep flag """
return self.keep_obj
class ReorderIds(tool.BatchTool, ManagedWindow, UpdateCallback):
""" Class for Reodering Gramps ID Tool """
xobjects = (('person', 'people'), ('family', 'families'),
('event', 'events'), ('place', 'places'),
('source', 'sources'), ('citation', 'citations'),
('repository', 'repositories'),
('media', 'media'), ('note', 'notes'))
def build_menu_names_(self, widget=None):
""" The menu name """
return (_('Main window'), _("Reorder Gramps IDs"))
def __init__(self, dbstate, user, options_class, name, callback=None):
self.uistate = user.uistate
self.db = dbstate.db
if self.uistate:
tool.BatchTool.__init__(self, dbstate, user, options_class, name)
if self.fail:
return # user denied to modify Gramps IDs
ManagedWindow.__init__(self, self.uistate, [], self.__class__)
if not self.uistate:
UpdateCallback.__init__(self, user.callback)
self.object_status = True
self.change_status = False
self.start_zero = True
self.step_cnt, self.step_list = 0, ['1', '2', '5', '10']
self.keep_status = True
self.obj_values = {} # enable access to all internal values
self.active_entries, self.format_entries = {}, {}
self.change_entries = {}
self.start_entries, self.step_entries = {}, {}
self.keep_entries = {}
self.prim_methods, self.obj_methods = {}, {}
for prim_obj, prim_objs in self.xobjects:
iter_handles = "iter_%s_handles" % prim_obj
get_number_obj = "get_number_of_%s" % prim_objs
prefix_fmt = "%s_prefix" % prim_obj
get_from_id = "get_%s_from_gramps_id" % prim_obj
get_from_handle = "get_%s_from_handle" % prim_obj
next_from_id = "find_next_%s_gramps_id" % prim_obj
commit = "commit_%s" % prim_obj
self.prim_methods[prim_obj] = (getattr(self.db, prefix_fmt),
getattr(self.db, get_number_obj)(),
getattr(self.db, next_from_id)())
self.obj_methods[prim_obj] = (getattr(self.db, iter_handles),
getattr(self.db, commit),
getattr(self.db, get_from_id),
getattr(self.db, get_from_handle),
getattr(self.db, next_from_id))
object_fmt, quant_id, next_id = self.prim_methods[prim_obj]
obj_value = ReorderEntry(object_fmt, quant_id, next_id, prim_obj)
self.obj_values[prim_obj] = obj_value
if self.uistate:
self._display()
else:
self._execute()
def __on_object_button_clicked(self, widget=None):
""" compute all primary objects and toggle the 'Active' attribute """
self.object_status = not self.object_status
for prim_obj, dummy in self.xobjects:
obj = self.top.get_object('%s_active' % prim_obj)
obj.set_active(self.object_status)
def __on_object_button_toggled(self, widget):
""" compute the primary object and toggle the 'Sensitive' attribute """
obj_state = widget.get_active()
obj_name = Gtk.Buildable.get_name(widget).split('_', 1)[0]
self.active_entries[obj_name].set_val(obj_state)
for obj_entry in ['actual', 'quant', 'format', 'change']:
obj = self.top.get_object('%s_%s' % (obj_name, obj_entry))
obj.set_sensitive(obj_state)
for obj_entry in ['start', 'step', 'keep']:
obj = self.top.get_object('%s_change' % obj_name)
if obj.get_active():
obj = self.top.get_object('%s_%s' % (obj_name, obj_entry))
obj.set_sensitive(obj_state)
def __on_format_button_clicked(self, widget=None):
""" compute all sensitive primary objects and sets the
'Format' scheme of identifiers """
for prim_obj, dummy in self.xobjects:
obj_format = self.top.get_object('%s_format' % prim_obj)
if not obj_format.get_sensitive():
continue
obj_fmt = self.obj_values[prim_obj].res_fmt()
self.format_entries[prim_obj].force_value(obj_fmt)
if self.start_zero:
obj_id = self.obj_values[prim_obj].zero_id()
else:
obj_id = self.obj_values[prim_obj].last_id()
self.start_entries[prim_obj].force_value(obj_id)
def __on_change_button_clicked(self, widget=None):
""" compute all primary objects and toggle the 'Change' attribute """
self.change_status = not self.change_status
for prim_obj, dummy in self.xobjects:
obj_change = self.top.get_object('%s_change' % prim_obj)
if not obj_change.get_sensitive():
continue
self.change_entries[prim_obj].set_val(self.change_status)
obj_change.set_active(self.change_status)
def __on_change_button_toggled(self, widget):
""" compute the primary object and toggle the 'Sensitive' attribute """
obj_state = widget.get_active()
obj_name = Gtk.Buildable.get_name(widget).split('_', 1)[0]
for obj_entry in ['start', 'step', 'keep']:
obj = self.top.get_object('%s_%s' % (obj_name, obj_entry))
if obj_entry == 'keep':
if (self.obj_values[obj_name].stored_prefix !=
self.obj_values[obj_name].object_prefix and
self.obj_values[obj_name].stored_suffix !=
self.obj_values[obj_name].object_suffix):
self.keep_entries[obj_name].set_val(False)
else:
obj.set_active(obj_state)
self.keep_entries[obj_name].set_val(obj_state)
obj.set_sensitive(obj_state)
def __on_start_button_clicked(self, widget=None):
""" compute all sensitive primary objects and sets the
'Start' values of identifiers """
self.start_zero = not self.start_zero
for prim_obj, dummy in self.xobjects:
obj = self.top.get_object('%s_start' % prim_obj)
if not obj.get_sensitive():
continue
if self.start_zero:
obj_id = self.obj_values[prim_obj].zero_id()
else:
obj_id = self.obj_values[prim_obj].next_id()
self.start_entries[prim_obj].force_value(obj_id)
def __on_step_button_clicked(self, widget=None):
""" compute all sensitive primary objects and sets the
'Step' width of identifiers """
self.step_cnt = self.step_cnt + 1 if self.step_cnt < 3 else 0
for prim_obj, dummy in self.xobjects:
obj = self.top.get_object('%s_step' % prim_obj)
if not obj.get_sensitive():
continue
step_val = self.step_list[self.step_cnt]
self.step_entries[prim_obj].force_value(step_val)
def __on_keep_button_clicked(self, widget=None):
""" compute the primary object and toggle the 'Active' attribute """
self.keep_status = not self.keep_status
for prim_obj, dummy in self.xobjects:
obj = self.top.get_object('%s_change' % prim_obj)
if not obj.get_active():
continue
obj = self.top.get_object('%s_keep' % prim_obj)
obj.set_active(self.keep_status)
self.keep_entries[prim_obj].set_val(self.keep_status)
def __on_format_entry_keyrelease(self, widget, event, data=None):
""" activated on all return's of an entry """
if event.keyval in [Gdk.KEY_Return]:
obj_name = Gtk.Buildable.get_name(widget).split('_', 1)[0]
obj_fmt = self.format_entries[obj_name].get_val()
self.format_entries[obj_name].force_value(obj_fmt)
self.start_entries[obj_name].update()
obj_change = self.top.get_object('%s_change' % obj_name)
obj_change.grab_focus()
return False
def __on_format_entry_focusout(self, widget, event, data=None):
""" activated on all focus out of an entry """
obj_name = Gtk.Buildable.get_name(widget).split('_', 1)[0]
obj_fmt = self.format_entries[obj_name].get_val()
self.format_entries[obj_name].set_text(obj_fmt)
self.start_entries[obj_name].update()
return False
def __on_start_entry_focusout(self, widget, event, data=None):
""" activated on all focus out of an entry """
obj_name = Gtk.Buildable.get_name(widget).split('_', 1)[0]
self.start_entries[obj_name].update()
return False
def __on_ok_button_clicked(self, widget=None):
""" execute the reodering and close """
self._execute()
self._update()
self.close()
def __on_cancel_button_clicked(self, widget=None):
""" cancel the reodering and close """
self.close()
def __on_help_button_clicked(self, widget=None):
""" display the relevant portion of Gramps manual """
display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC)
def _display(self):
""" organize Glade 'Reorder IDs' window """
# get the main window from glade
self.top = Glade(toplevel="reorder-ids")
window = self.top.toplevel
# set gramps style title for the window
self.set_window(window, self.top.get_object("title"),
_("Reorder Gramps IDs"))
# connect signals
self.top.connect_signals({
"on_object_button_clicked" : self.__on_object_button_clicked,
"on_object_button_toggled" : self.__on_object_button_toggled,
"on_format_button_clicked" : self.__on_format_button_clicked,
"on_start_button_clicked" : self.__on_start_button_clicked,
"on_step_button_clicked" : self.__on_step_button_clicked,
"on_keep_button_clicked" : self.__on_keep_button_clicked,
"on_change_button_clicked" : self.__on_change_button_clicked,
"on_change_button_toggled" : self.__on_change_button_toggled,
"on_format_entry_keyrelease" : self.__on_format_entry_keyrelease,
"on_format_entry_focusout" : self.__on_format_entry_focusout,
"on_start_entry_focusout" : self.__on_start_entry_focusout,
"on_help_button_clicked" : self.__on_help_button_clicked,
"on_cancel_button_clicked" : self.__on_cancel_button_clicked,
"on_ok_button_clicked" : self.__on_ok_button_clicked
})
# Calculate all entries and update Glade window
for prim_obj, dummy in self.xobjects:
# populate Object, Actual & Quantity fields with values
obj_active = self.top.get_object('%s_active' % prim_obj)
self.active_entries[prim_obj] = MonitoredCheckbox(
obj_active, obj_active, self.obj_values[prim_obj].set_active,
self.obj_values[prim_obj].get_active)
obj_actual = self.top.get_object('%s_actual' % prim_obj)
obj_actual.set_text('%s' % self.obj_values[prim_obj].last_id())
obj_quant = self.top.get_object('%s_quant' % prim_obj)
obj_quant.set_text('%s' % str(self.obj_values[prim_obj].quant_id))
# connect/populate Format, Start, Step, Keep & Change fields
# with GTK/values
obj_format = self.top.get_object('%s_format' % prim_obj)
self.format_entries[prim_obj] = MonitoredEntry(
obj_format, self.obj_values[prim_obj].set_fmt,
self.obj_values[prim_obj].get_fmt)
obj_change = self.top.get_object('%s_change' % prim_obj)
self.change_entries[prim_obj] = MonitoredCheckbox(
obj_change, obj_change, self.obj_values[prim_obj].set_change,
self.obj_values[prim_obj].get_change)
obj_start = self.top.get_object('%s_start' % prim_obj)
self.start_entries[prim_obj] = MonitoredEntry(
obj_start, self.obj_values[prim_obj].set_id,
self.obj_values[prim_obj].get_id)
obj_step = self.top.get_object('%s_step' % prim_obj)
self.step_entries[prim_obj] = MonitoredEntry(
obj_step, self.obj_values[prim_obj].set_step,
self.obj_values[prim_obj].get_step,
changed=self.obj_values[prim_obj].change_step)
obj_keep = self.top.get_object('%s_keep' % prim_obj)
self.keep_entries[prim_obj] = MonitoredCheckbox(
obj_keep, obj_keep, self.obj_values[prim_obj].set_keep,
self.obj_values[prim_obj].get_keep, readonly=True)
# fetch the popup menu
self.menu = self.top.get_object("popup_menu")
# ok, let's see what we've done
self.window.resize(700, 410)
self.show()
def _update(self):
""" store changed objects formats in DB """
update = False
for prim_obj, dummy in self.xobjects:
obj_value = self.obj_values[prim_obj]
if obj_value.object_fmt != obj_value.stored_fmt:
constant = 'preferences.%sprefix' % PREFIXES[prim_obj]
config.set(constant, obj_value.object_fmt)
update = True
if update:
config.save()
self.db.set_prefixes(
config.get('preferences.iprefix'),
config.get('preferences.oprefix'),
config.get('preferences.fprefix'),
config.get('preferences.sprefix'),
config.get('preferences.cprefix'),
config.get('preferences.pprefix'),
config.get('preferences.eprefix'),
config.get('preferences.rprefix'),
config.get('preferences.nprefix'))
def _execute(self):
""" execute all primary objects and reorder if neccessary """
# Update progress calculation
if self.uistate:
self.progress = ProgressMeter(_('Reorder Gramps IDs'), '')
else:
total_objs = 0
for prim_obj, dummy in self.xobjects:
if self.obj_values[prim_obj].active_obj:
total_objs += self.obj_values[prim_obj].quant_id
self.set_total(total_objs)
# Update database
self.db.disable_signals()
for prim_obj, prim_objs in self.xobjects:
with DbTxn(_('Reorder %s IDs ...') % prim_obj,
self.db, batch=True) as self.trans:
if self.obj_values[prim_obj].active_obj:
if self.uistate:
self.progress.set_pass(
_('Reorder %s IDs ...') % _(prim_objs.title()),
self.obj_values[prim_obj].quant_id)
# Process reordering
self._reorder(prim_obj)
self.db.enable_signals()
self.db.request_rebuild()
# Update progress calculation
if self.uistate:
self.progress.close()
else:
print('\nDone.')
# finds integer portion in a GrampsID
_findint = re.compile(r'^[^\d]*(\d+)[^\d]*$')
# finds prefix, number, suffix of a Gramps ID ignoring a leading or
# trailing space. The number must be at least three digits.
_prob_id = re.compile(r'^ *([^\d]*)(\d{3,9})([^\d]*) *$')
def _reorder(self, prim_obj):
""" reorders all selected objects with a (new) style, start & step """
dup_ids = [] # list of duplicate identifiers
new_ids = {} # list of new identifiers
iter_handles, commit, get_from_id, get_from_handle, next_from_id = \
self.obj_methods[prim_obj]
prefix_fmt = self.obj_values[prim_obj].get_fmt()
prefix = self.obj_values[prim_obj].object_prefix
suffix = self.obj_values[prim_obj].object_suffix
old_pref = self.obj_values[prim_obj].stored_prefix
old_suff = self.obj_values[prim_obj].stored_suffix
new_id = self.obj_values[prim_obj].get_id()
keep_fmt = self.obj_values[prim_obj].get_keep()
change = self.obj_values[prim_obj].get_change()
index_max = int("9" * self.obj_values[prim_obj].width_fmt)
do_same = False
for handle in iter_handles():
# Update progress
if self.uistate:
self.progress.step()
else:
self.update()
# extract basic data out of the database
obj = get_from_handle(handle)
act_id = obj.get_gramps_id()
# here we see if the ID looks like a new or previous or default
# Gramps ID.
# If not we ask user if he really wants to replace it.
# This should allow user to protect a GetGov ID or similar
match = self._prob_id.match(act_id)
if not (match and
(prefix == match.groups()[0] and
suffix == match.groups()[2] or
old_pref == match.groups()[0] and
old_suff == match.groups()[2] or
len(match.groups()[0]) == 1 and
len(match.groups()[2]) == 0)) and not do_same:
xml = Glade(toplevel='dialog')
top = xml.toplevel
# self.top.set_icon(ICON)
top.set_title("%s - Gramps" % _("Reorder Gramps IDs"))
apply_to_rest = xml.get_object('apply_to_rest')
label1 = xml.get_object('toplabel')
label1.set_text('<span weight="bold" size="larger">%s</span>' %
_("Reorder Gramps IDs"))
label1.set_use_markup(True)
label2 = xml.get_object('mainlabel')
label2.set_text(_("Do you want to replace %s?" % act_id))
top.set_transient_for(self.progress._ProgressMeter__dialog)
self.progress._ProgressMeter__dialog.set_modal(False)
top.show()
response = top.run()
do_same = apply_to_rest.get_active()
top.destroy()
self.progress._ProgressMeter__dialog.set_modal(True)
if response != Gtk.ResponseType.YES:
continue
elif not match and do_same and response != Gtk.ResponseType.YES:
continue
if change:
# update the defined ID numbers into objects under
# consideration of keeping ID if format not matches prefix
# (implication logical boolean operator below)
if act_id.startswith(prefix) and act_id.endswith(suffix) or \
not keep_fmt:
obj.set_gramps_id(new_id)
commit(obj, self.trans)
new_id = self.obj_values[prim_obj].succ_id()
else:
# attempt to extract integer - if we can't, treat it as a
# duplicate
try:
match = self._findint.match(act_id)
if match:
# get the integer, build the new handle. Make sure it
# hasn't already been chosen. If it has, put this
# in the duplicate handle list
index = int(match.groups()[0])
if index > index_max:
new_id = next_from_id()
else:
new_id = prefix_fmt % index
if new_id == act_id:
if new_id in new_ids:
dup_ids.append(obj.get_handle())
else:
new_ids[new_id] = act_id
elif get_from_id(new_id) is not None:
dup_ids.append(obj.get_handle())
else:
obj.set_gramps_id(new_id)
commit(obj, self.trans)
new_ids[new_id] = act_id
else:
dup_ids.append(handle)
except:
dup_ids.append(handle)
# go through the duplicates, looking for the first available
# handle that matches the new scheme.
if dup_ids:
if self.uistate:
self.progress.set_pass(_('Finding and assigning unused IDs.'),
len(dup_ids))
for handle in dup_ids:
obj = get_from_handle(handle)
obj.set_gramps_id(next_from_id())
commit(obj, self.trans)
#------------------------------------------------------------------------
#
#
#
#------------------------------------------------------------------------
class ReorderIdsOptions(tool.ToolOptions):
""" Defines options and provides handling interface. """
def __init__(self, name, person_id=None):
tool.ToolOptions.__init__(self, name, person_id)