gramps/src/plugins/Query.py

442 lines
16 KiB
Python
Raw Normal View History

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007-2008 Brian G. Matherly
#
# 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
#
"""
Run a query on the tables
"""
from Simple import SimpleAccess, SimpleDoc, SimpleTable
from gettext import gettext as _
from PluginUtils import PluginManager
import Utils
from ReportBase import CATEGORY_QR_MISC
import DateHandler
import gen.lib
import Config
def cleanup_column_name(column):
""" Handle column aliases for CSV spreadsheet import and SQL """
retval = column
# Title case:
if retval in ["Lastname",
"Surname", _("Surname")]:
return "surname"
elif retval in ["Firstname",
"Given name", _("Given name"),
"Given", _("Given")]:
return "firstname"
elif retval in ["Callname",
"Call name", _("Call name"),
"Call", _("Call")]:
return "callname"
elif retval in ["Title", _("Title")]:
return "title"
elif retval in ["Prefix", _("Prefix")]:
return "prefix"
elif retval in ["Suffix", _("Suffix")]:
return "suffix"
elif retval in ["Gender", _("Gender")]:
return "gender"
elif retval in ["Source", _("Source")]:
return "source"
elif retval in ["Note", _("Note")]:
return "note"
elif retval in ["Birthplace",
"Birth place", _("Birth place")]:
return "birthplace"
elif retval in ["Birthdate",
"Birth date", _("Birth date")]:
return "birthdate"
elif retval in ["Birthsource",
"Birth source", _("Birth source")]:
return "birthsource"
elif retval in ["Deathplace",
"Death place", _("Death place")]:
return "deathplace"
elif retval in ["Deathdate",
"Death date", _("Death date")]:
return "deathdate"
elif retval in ["Deathsource",
"Death source", _("Death source")]:
return "deathsource"
elif retval in ["Deathcause",
"Death cause", _("Death cause")]:
return "deathcause"
elif retval in ["Grampsid", "ID",
"Gramps id", _("Gramps id")]:
return "grampsid"
elif retval in ["Person", _("Person")]:
return "person"
# ----------------------------------
elif retval in ["Child", _("Child")]:
return "child"
elif retval in ["Source", _("Source")]:
return "source"
elif retval in ["Family", _("Family")]:
return "family"
# ----------------------------------
elif retval in ["Mother", _("Mother"),
"Wife", _("Wife"),
"Parent2", _("Parent2")]:
return "wife"
elif retval in ["Father", _("Father"),
"Husband", _("Husband"),
"Parent1", _("Parent1")]:
return "husband"
elif retval in ["Marriage", _("Marriage")]:
return "marriage"
elif retval in ["Date", _("Date")]:
return "date"
elif retval in ["Place", _("Place")]:
return "place"
# lowercase
elif retval in ["lastname", "last_name",
"surname", _("surname")]:
return "surname"
elif retval in ["firstname", "first_name", "given_name",
"given name", _("given name"),
"given", _("given")]:
return "firstname"
elif retval in ["callname", "call_name",
"call name",
"call", _("call")]:
return "callname"
elif retval in ["title", _("title")]:
return "title"
elif retval in ["prefix", _("prefix")]:
return "prefix"
elif retval in ["suffix", _("suffix")]:
return "suffix"
elif retval in ["gender", _("gender")]:
return "gender"
elif retval in ["source", _("source")]:
return "source"
elif retval in ["note", _("note")]:
return "note"
elif retval in ["birthplace", "birth_place",
"birth place", _("birth place")]:
return "birthplace"
elif retval in ["birthdate", "birth_date",
"birth date", _("birth date")]:
return "birthdate"
elif retval in ["birthsource", "birth_source",
"birth source", _("birth source")]:
return "birthsource"
elif retval in ["deathplace", "death_place",
"death place", _("death place")]:
return "deathplace"
elif retval in ["deathdate", "death_date",
"death date", _("death date")]:
return "deathdate"
elif retval in ["deathsource", "death_source",
"death source", _("death source")]:
return "deathsource"
elif retval in ["deathcause", "death_cause",
"death cause", _("death cause")]:
return "deathcause"
elif retval in ["grampsid", "id", "gramps_id",
"gramps id", _("gramps id")]:
return "grampsid"
elif retval in ["person", _("person")]:
return "person"
# ----------------------------------
elif retval in ["child", _("child")]:
return "child"
elif retval in ["source", _("source")]:
return "source"
elif retval in ["family", _("family")]:
return "family"
# ----------------------------------
elif retval in ["mother", _("mother"),
"wife", _("wife"),
"parent2", _("parent2")]:
return "wife"
elif retval in ["father", _("father"),
"husband", _("husband"),
"parent1", _("parent1")]:
return "husband"
elif retval in ["marriage", _("marriage")]:
return "marriage"
elif retval in ["date", _("date")]:
return "date"
elif retval in ["place", _("place")]:
return "place"
#----------------------------------------------------
return retval
class DBI:
def __init__(self, database, document):
self.database = database
self.document = document
def parse(self, query):
# select col1, col2 from table where exp;
# select * from table where exp;
# delete from table where exp;
self.query = query
state = "START"
data = None
i = 0
self.columns = []
self.command = None
self.where = None
self.table = None
while i < len(query):
c = query[i]
#print "STATE:", state, c
if state == "START":
if c in [' ', '\n', '\t']: # pre white space
pass # skip it
else:
state = "COMMAND"
data = c
elif state == "COMMAND":
if c in [' ', '\n', '\t']: # ending white space
self.command = data.lower()
data = ''
state = "AFTER-COMMAND"
else:
data += c
elif state == "AFTER-COMMAND":
if c in [' ', '\n', '\t']: # pre white space
pass
else:
state = "COL_OR_FROM"
i -= 1
elif state == "COL_OR_FROM":
if c in [' ', '\n', '\t', ',']: # end white space or comma
if data.upper() == "FROM":
data = ''
state = "PRE-GET-TABLE"
else:
self.columns.append(data.lower())
data = ''
state = "AFTER-COMMAND"
else:
data += c
elif state == "PRE-GET-TABLE":
if c in [' ', '\n', '\t']: # pre white space
pass
else:
state = "GET-TABLE"
i -= 1
elif state == "GET-TABLE":
if c in [' ', '\n', '\t', ';']: # end white space or colon
self.table = data.lower()
data = ''
state = "PRE-GET-WHERE"
else:
data += c
elif state == "PRE-GET-WHERE":
if c in [' ', '\n', '\t']: # pre white space
pass
else:
state = "GET-WHERE"
i -= 1
elif state == "GET-WHERE":
if c in [' ', '\n', '\t']: # end white space
if data.upper() != "WHERE":
raise AttributeError("expecting WHERE got '%s'" % data)
else:
data = ''
state = "GET-EXP"
else:
data += c
elif state == "GET-EXP":
self.where = query[i:]
self.where = self.where.strip()
if self.where.endswith(";"):
self.where = self.where[:-1]
i = len(query)
else:
raise AttributeError("unknown state: '%s'" % state)
i += 1
if self.table is None:
raise AttributeError("malformed query: no table in '%s'\n" % self.query)
def close(self):
try:
self.progress.close()
except:
pass
def eval(self):
self.sdb = SimpleAccess(self.database)
self.stab = SimpleTable(self.sdb)
self.select = 0
self.progress = Utils.ProgressMeter(_('Processing Query'))
self.process_table()
if self.select > 0:
self.sdoc = SimpleDoc(self.document)
self.sdoc.title(self.query)
self.sdoc.paragraph("\n")
self.sdoc.paragraph("%d rows processed.\n" % self.select)
self.stab.write(self.sdoc)
self.sdoc.paragraph("")
return _("[%d rows processed]") % self.select
def get_columns(self, table):
if table == "people":
return ("name", "grampsid", "gender",
"birth_date", "birth_place",
"death_date", "death_place",
"change", "marker", "private")
elif table == "events":
return ("grampsid", "type", "date",
"description", "place",
"change", "marker", "private")
# families = grampsid, father, mother, relationship, date
# sources =
# places =
# media =
# repos =
# notes =
else:
raise AttributeError("unknown table: '%s'" % table)
def process_table(self):
for col_name in self.columns[:]: # copy
if col_name == "*":
self.columns.remove('*')
self.columns.extend( self.get_columns(self.table))
self.stab.columns(*map(lambda s: s.replace("_", "__"),
self.columns))
if self.table == "people":
all_people = [person for person in self.sdb.all_people()]
if len(all_people) > 100:
self.progress.set_pass(_('Matching people...'), len(all_people)/50)
count = 0
for person in all_people:
if len(all_people) > 100:
if count % 50 == 0:
self.progress.step()
count += 1
row = []
sorts = []
env = {_("Date"): gen.lib.date.Date} # env for py.eval
for col_name in self.columns:
col = cleanup_column_name(col_name)
if col == "name":
env[col_name] = str(person)
row.append(person)
elif col == "firstname":
env[col_name] = person.get_primary_name().get_first_name()
row.append(env[col_name])
elif col == "surname":
env[col_name] = person.get_primary_name().get_surname()
row.append(env[col_name])
elif col == "suffix":
env[col_name] = person.get_primary_name().get_suffix()
row.append(env[col_name])
elif col == "title":
env[col_name] = person.get_primary_name().get_title()
row.append(env[col_name])
elif col == "birthdate":
env[col_name] = self.sdb.birth_date_obj(person)
row.append(env[col_name])
elif col == "deathdate":
env[col_name] = self.sdb.death_date_obj(person)
row.append(env[col_name])
elif col == "gender":
env[col_name] = self.sdb.gender(person)
row.append(env[col_name])
elif col == "birthplace":
env[col_name] = self.sdb.birth_place(person)
row.append(env[col_name])
elif col == "deathplace":
env[col_name] = self.sdb.death_place(person)
row.append(env[col_name])
elif col == "change":
env[col_name] = person.get_change_display()
row.append(env[col_name])
elif col == "marker":
env[col_name] = person.marker.string
row.append(env[col_name])
elif col == "private":
env[col_name] = {True: "private", False: ""}[person.private]
row.append(env[col_name])
elif col == "grampsid":
env[col_name] = person.gramps_id
row.append(env[col_name])
else:
raise AttributeError("unknown column: '%s'" % col_name)
#sorts.append((len(row)-1,sortval))
# should we add it?:
if self.where:
try:
result = eval(self.where, env)
except:
raise AttributeError("malformed where clause: '%s'" % self.where)
result = False
else:
result = True
if result:
self.select += 1
if self.command == "select":
self.stab.row(*row)
for (col, value) in sorts:
self.stab.row_sort_val(col, value)
elif self.command == "delete":
self.database.active = person
trans = self.database.transaction_begin()
active_name = _("Delete Person (%s)") % self.sdb.name(person)
gen.utils.delete_person_from_database(self.database, person, trans)
# FIXME: delete familes, events, notes, resources, etc, if possible
self.database.transaction_commit(trans, active_name)
else:
raise AttributeError("unknown command: '%s'", self.command)
def run(database, document, query):
"""
"""
retval = ""
dbi = DBI(database, document)
try:
q = dbi.parse(query)
except AttributeError, msg:
return msg
try:
retval = dbi.eval()
except AttributeError, msg:
# dialog?
retval = msg
dbi.close()
return retval
#------------------------------------------------------------------------
#
# Register the report
#
#------------------------------------------------------------------------
pmgr = PluginManager.get_instance()
pmgr.register_quick_report(
name = 'query',
category = CATEGORY_QR_MISC,
run_func = run,
translated_name = _("Query"),
status = _("Stable"),
description= _("Display data that matches a query"),
author_name="Douglas Blank",
author_email="dblank@cs.brynmawr.edu"
)