diff --git a/gramps/gen/lib/__init__.py b/gramps/gen/lib/__init__.py index 33f1faee7..df6341605 100644 --- a/gramps/gen/lib/__init__.py +++ b/gramps/gen/lib/__init__.py @@ -2,7 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham -# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2011-2013 Tim G L Lyons # # 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 @@ -57,6 +57,7 @@ from .citation import Citation # Logical objects from .srctemplate import SrcTemplate +from .srctemplatelist import SrcTemplateList # Table objects from .tag import Tag diff --git a/gramps/gen/lib/citation.py b/gramps/gen/lib/citation.py index 04d2420c9..0808fbe6d 100644 --- a/gramps/gen/lib/citation.py +++ b/gramps/gen/lib/citation.py @@ -3,7 +3,7 @@ # # Copyright (C) 2000-2007 Donald N. Allingham # Copyright (C) 2010 Michiel D. Nauta -# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2011-2013 Tim G L Lyons # # 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 @@ -46,6 +46,7 @@ from .datebase import DateBase from .tagbase import TagBase from .srcattrbase import SrcAttributeBase from .srctemplate import SrcTemplate +from .srctemplatelist import SrcTemplateList from ..constfunc import cuni, deprecated from .handle import Handle @@ -341,7 +342,7 @@ class Citation(MediaBase, NoteBase, SrcAttributeBase, PrimaryObject, DateBase): """ attrlist = self.get_attribute_list() if templatekey: - stemp = SrcTemplate(templatekey) + stemp = SrcTemplateList().get_template_from_name(templatekey) return stemp.page_gedcom(attrlist) else: return self.get_name() diff --git a/gramps/gen/lib/src.py b/gramps/gen/lib/src.py index 012cf41be..32083f6f5 100644 --- a/gramps/gen/lib/src.py +++ b/gramps/gen/lib/src.py @@ -37,6 +37,7 @@ from .notebase import NoteBase from .tagbase import TagBase from .srcattrbase import SrcAttributeBase from .srctemplate import SrcTemplate +from .srctemplatelist import SrcTemplateList from .reporef import RepoRef from .const import DIFFERENT, EQUAL, IDENTICAL from ..constfunc import cuni, deprecated @@ -449,7 +450,7 @@ class Source(MediaBase, NoteBase, SrcAttributeBase, PrimaryObject): :rtype: str """ attrlist = self.get_attribute_list() - stemp = SrcTemplate(self.get_template()) + stemp = SrcTemplateList().get_template_from_name(self.get_template()) return stemp.title_gedcom(attrlist) @@ -461,7 +462,7 @@ class Source(MediaBase, NoteBase, SrcAttributeBase, PrimaryObject): 3. if no template, it defaults to GEDCOM, so AUTHOR will be used """ attrlist = self.get_attribute_list() - stemp = SrcTemplate(self.get_template()) + stemp = SrcTemplateList().get_template_from_name(self.get_template()) return stemp.author_gedcom(attrlist) @@ -473,6 +474,6 @@ class Source(MediaBase, NoteBase, SrcAttributeBase, PrimaryObject): 3. if no template, it defaults to GEDCOM, so PUB_INFO will be used """ attrlist = self.get_attribute_list() - stemp = SrcTemplate(self.get_template()) + stemp = SrcTemplateList().get_template_from_name(self.get_template()) return stemp.pubinfo_gedcom(attrlist) diff --git a/gramps/gen/lib/srctemplate.py b/gramps/gen/lib/srctemplate.py index 752d7c120..742858f35 100644 --- a/gramps/gen/lib/srctemplate.py +++ b/gramps/gen/lib/srctemplate.py @@ -3,6 +3,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2013 Benny Malengier +# Copyright (C) 2013 Tim G L Lyons # # 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 @@ -36,6 +37,14 @@ from ..const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext import csv +#------------------------------------------------------------------------ +# +# Set up logging +# +#------------------------------------------------------------------------ +import logging +LOG = logging.getLogger('.template') + #------------------------------------------------------------------------- # # GRAMPS modules @@ -43,6 +52,10 @@ import csv #------------------------------------------------------------------------- from .srcattrtype import * from .date import Date +from .tableobj import TableObject +from .secondaryobj import SecondaryObject +from .handle import Handle +from ..constfunc import cuni #columns in a csv file defining templates NRCOL = 0 @@ -67,171 +80,13 @@ TOOLTIPCOL = 17 UNKNOWN = 'UNKNOWN' DESCR = -10 -# the GEDCOM type is predefined and always present. Other templates will be -# loaded via plugins -EVIDENCETEMPLATES = { - 'GEDCOM': { - REF_TYPE_L: [ - ('', SrcAttributeType.AUTHOR, _(''), '.', EMPTY, False, False, EMPTY, GED_AUTHOR, - None, None), - ('', SrcAttributeType.TITLE, _(''), '.', STYLE_QUOTE, False, False, EMPTY, GED_TITLE, - None, None), - ('', SrcAttributeType.PUB_INFO, _(''), '', EMPTY, False, False, EMPTY, GED_PUBINF, - None, None), - ], - REF_TYPE_F: [ - ('', SrcAttributeType.AUTHOR, _(''), ',', EMPTY, False, False, EMPTY, EMPTY, - None, None), - ('', SrcAttributeType.TITLE, _(''), ',', STYLE_QUOTE, False, False, EMPTY, EMPTY, - None, None), - ('', SrcAttributeType.PUB_INFO, _(''), '.', EMPTY, False, False, EMPTY, EMPTY, - None, None), - ('', SrcAttributeType.DATE, _(''), ' -', EMPTY, False, False, EMPTY, EMPTY, - None, None), - ('', SrcAttributeType.PAGE, _('Page(s)'), '.', EMPTY, False, False, EMPTY, EMPTY, - None, None), - ], - REF_TYPE_S: [ - ('', SrcAttributeType.AUTHOR, _(''), ',', EMPTY, False, False, EMPTY, EMPTY, - None, None), - ('', SrcAttributeType.DATE, _(''), ' -', EMPTY, False, False, EMPTY, EMPTY, - None, None), - ('', SrcAttributeType.PAGE, _('Page(s)'), '.', EMPTY, False, False, EMPTY, EMPTY, - None, None), - ], - DESCR: '%(first)s - %(sec)s - %(third)s' % { 'first': _('Basic'), 'sec': _('GEDCOM Style'), 'third': _('')}, - }, - UNKNOWN: { - REF_TYPE_L: [ - ], - REF_TYPE_F: [ - ], - REF_TYPE_S: [ - ], - DESCR: _("Unrecognized Template. Download it's definition."), - }, - } - -def load_srctemplates_data(): - """ - Loads the srctemplates defined, and returns a dict with template data - """ - from gramps.gen.plug import BasePluginManager - bpmgr = BasePluginManager.get_instance() - pdatas = bpmgr.get_reg_srctemplates() - templatemap = {} - - for plugin in pdatas: - mod = bpmgr.load_plugin(plugin) - if mod: - csvfilename = mod.csvfile - with open(csvfilename, 'rb') as csvfile: - templatemap.update(load_srctemplate_csv(csvfile)) - return templatemap - -def load_srctemplate_csv(csvfile): - """ - Loads a template csvfile, and returns a dict with template data - Note: csvfile could be a list containing strings! - """ - first = True - TYPE2CITEMAP = {} - CITE_TYPES = {'F': REF_TYPE_F, 'L': REF_TYPE_L, 'S': REF_TYPE_S} - GEDCOMFIELDS = {'A': GED_AUTHOR, 'T': GED_TITLE, - 'P': GED_PUBINF, 'D': GED_DATE} - SHORTERALG = {'LOC': SHORTERALG_LOC, 'YEAR': SHORTERALG_YEAR, - 'ETAL': SHORTERALG_ETAL, 'REV.': SHORTERALG_REVERT_TO_DOT} - STYLES = {'Quoted': STYLE_QUOTE, 'Italics': STYLE_EMPH, - 'QuotedCont': STYLE_QUOTECONT, 'Bold': STYLE_BOLD} - - reader = csv.reader(csvfile, delimiter=';') - - prevtempl = '' - newtempl = True - for row in reader: - if first: - #skip first row with headers - first=False - continue - - if row[CATCOL]: - cat = row[CATCOL].strip() - cattype = row[CATTYPECOL].strip() - types = row[TYPECOL].strip() - descr = row[DESCRCOL].strip() - source_type = row[IDENTCOL].strip() - if prevtempl != source_type: - newtempl = True - prevtempl = source_type - else: - newtempl = False - if descr: - source_descr = '%s - %s - %s (%s)' % (_(cat), _(cattype), - _(types), _(descr)) - else: - source_descr = '%s - %s - %s' % (_(cat), _(cattype), _(types)) - if source_type in TYPE2CITEMAP: - if not TYPE2CITEMAP[source_type] [DESCR] == source_descr: - raise NotImplementedError, source_type + ' ' + TYPE2CITEMAP[source_type] [DESCR] + ' NOT equal to known description' + source_descr - if newtempl: - #the template is new in this csv, but already defined, probably user error - raise NotImplementedError, 'Source template ' + prevtempl + ' is twice defined in the csv.' - else: - TYPE2CITEMAP[source_type] = {REF_TYPE_L: [], REF_TYPE_F: [], - REF_TYPE_S: [], DESCR: source_descr} - - if row[CITETYPECOL]: - #new citation type, - cite_type = row[CITETYPECOL].strip() - assert cite_type in ['F', 'L', 'S'], str(cite_type) - if cite_type == 'S': - shortcite = True - else: - shortcite = False - cite_type = CITE_TYPES[cite_type] - #add field for template to evidence style - field = row[FIELDCOL].strip() - field_type = field.replace('[', '').replace(']','').lower().capitalize() - #field_type = field.replace(' ', '_').replace("'","")\ - # .replace('&','AND').replace('(', '6').replace(')','9')\ - # .replace('[', '').replace(']','').replace('/', '_OR_')\ - # .replace(',', '').replace('.', '').replace(':', '')\ - # .replace('-', '_') - - #we need to force English SrcAttributeType - ifield_type = SrcAttributeType() - ifield_type.set_from_xml_str(field_type) - ifield_type = int(SrcAttributeType(ifield_type)) - if ifield_type == SrcAttributeType.CUSTOM: - raise NotImplementedError, "field must be a known SrcAttributeType, is " + str(SrcAttributeType(field_type)) - field_type = ifield_type - #field_descr = field.replace('[', '').replace(']','').lower().capitalize() - field_label = row[LABELCOL].strip() - #field_label = field_label.replace("'", "\\'") - private = False - if row[PRIVACYCOL].strip(): - private = True - optional = False - if row[OPTCOL].strip(): - optional = True - shorteralg = SHORTERALG.get(row[SHORTERCOL].strip()) or EMPTY - gedcommap = GEDCOMFIELDS.get(row[GEDCOMCOL].strip()) or EMPTY - style = STYLES.get(row[STYLECOL].strip()) or EMPTY - hint = row[HINTCOL] - tooltip = row[TOOLTIPCOL] - - TYPE2CITEMAP[source_type][cite_type] += [(row[LDELCOL], field_type, - _(field_label), row[RDELCOL], style, private, optional, - shorteralg, gedcommap, _(hint), _(tooltip))] - return TYPE2CITEMAP - #------------------------------------------------------------------------- # # SrcTemplate class # #------------------------------------------------------------------------- -class SrcTemplate(object): +class SrcTemplate(TableObject): """ Sources conform to a certain template, which governs their styling when used in reports. @@ -275,64 +130,115 @@ class SrcTemplate(object): 7/ the REF_TYPE_L reference maps to GEDCOM fields on export via this column. GEDCOM contains Title, Author and Pub.Info field """ - #on import, only default values present, plugins must still be loaded - MAP_LOADED = False - UNKNOWN = UNKNOWN def __init__(self, template_key=None): """ - Initialize the template from a given key. - If key is an integer, EVIDENCETEMPLATES is used of SrtAttrType. - If Key is string, it is first searched as the Key of EVIDENCETEMPLATES, - otherwise from xml templates (not implemented yet !!) + Create a new Template instance. + + After initialization, most data items have empty or null values, + including the database handle. """ - SrcTemplate.check_loaded() - if template_key is None: - template_key = UNKNOWN - self.set_template_key(template_key) + TableObject.__init__(self) + self.handle = "" + self.name = "" + self.descr = "" + self.template_element_list = [] + self.mapping_list = [] + self.structure = {REF_TYPE_L: [], REF_TYPE_F: [], + REF_TYPE_S: []} + self.empty() - @staticmethod - def check_loaded(): - """ we load first the map if needed + def serialize(self): """ - if not SrcTemplate.MAP_LOADED: - EVIDENCETEMPLATES.update(load_srctemplates_data()) - SrcTemplate.MAP_LOADED = True - - @staticmethod - def get_templatevalue_default(): - return 'GEDCOM' + Convert the data held in the Template to a Python tuple that + represents all the data elements. + + This method is used to convert the object into a form that can easily + be saved to a database. - @staticmethod - def template_defined(template_key): - """ - Return True if the given key is known, False otherwise - """ - SrcTemplate.check_loaded() - return template_key in EVIDENCETEMPLATES + These elements may be primitive Python types (string, integers), + complex Python types (lists or tuples, or Python objects. If the + target database cannot handle complex types (such as objects or + lists), the database is responsible for converting the data into + a form that it can use. - @staticmethod - def all_templates(): - SrcTemplate.check_loaded() - return [x for x in EVIDENCETEMPLATES.keys()] + :returns: Returns a python tuple containing the data that should + be considered persistent. + :rtype: tuple + """ + return ( + self.handle, + self.name, + self.descr, + [template_element.serialize() for template_element in self.template_element_list], + [mapping.serialize() for mapping in self.mapping_list], + ) - @staticmethod - def template_description(template_key): + def to_struct(self): """ - Return True if the given key is known, False otherwise - """ - SrcTemplate.check_loaded() - return EVIDENCETEMPLATES[template_key][DESCR] + Convert the data held in this object to a structure (eg, + struct) that represents all the data elements. + + This method is used to recursively convert the object into a + self-documenting form that can easily be used for various + purposes, including diffs and queries. - @staticmethod - def get_template(template_key): - """ - Return True if the given key is known, False otherwise - """ - SrcTemplate.check_loaded() - return EVIDENCETEMPLATES[template_key] + These structures may be primitive Python types (string, + integer, boolean, etc.) or complex Python types (lists, + tuples, or dicts). If the return type is a dict, then the keys + of the dict match the fieldname of the object. If the return + struct (or value of a dict key) is a list, then it is a list + of structs. Otherwise, the struct is just the value of the + attribute. + :returns: Returns a struct containing the data of the object. + :rtype: dict + """ + return {"handle": Handle("Srctemplate", self.handle), + "name": cuni(self.name), + "descr": cuni(self.descr), + "elements": [e.to_struct() for e in self.template_element_list], + "structure": (("%s: %s" % (s, self.structure[s])) for s in self.structure) + } + + def get_name(self): + return self.name + + def set_name(self, name): + self.name = name + + def get_descr(self): + return self.descr + + def set_descr(self, descr): + self.descr = descr + + def get_mapping_types_list(self): + return self.mapping_types_list + + def set_mapping_list(self, mapping_list): + self.mapping_list = mapping_list + + def add_mapping(self, mapping): + self.mapping_list.append(mapping) + + def get_template_element_list(self): + return self.template_element_list + + def set_template_element_list(self, template_element_list): + self.template_element_list = template_element_list + + def add_template_element(self, template_element): + if template_element not in self.template_element_list: + self.template_element_list.append(template_element) + + def add_structure_element(self, cite_type, slist): + self.structure[cite_type] += slist + + def get_structure(self): + return self.structure + def empty(self): """ remove all computed data @@ -346,24 +252,6 @@ class SrcTemplate(object): # normal value for ref F/S, short value ref S) self.attrmap = {} - def get_template_key(self): - """ - Obtain the current template key used - """ - return self.template_key - - def set_template_key(self, template_key): - """ - Change to the new template key for reference styling - """ - self.empty() - self.template_key = template_key - if template_key == UNKNOWN or template_key not in EVIDENCETEMPLATES: - #for key unknown we use styling according to GEDCOM - template_key = 'GEDCOM' - - self.tempstruct = EVIDENCETEMPLATES[template_key] - def set_attr_list(self, attr_list, attr_list_citation=None, date_citation=None): """ Set the attribute list of this template. Setting once for different @@ -481,8 +369,8 @@ class SrcTemplate(object): Construct a derived template reflist for use to construct the gedcom page field """ - reflist_F = self.tempstruct[REF_TYPE_F] - reflist_L_fields = [field[1] for field in self.tempstruct[REF_TYPE_L]] + reflist_F = self.structure[REF_TYPE_F] + reflist_L_fields = [field[1] for field in self.structure[REF_TYPE_L]] result = [] for entry in reflist_F: if entry[1] in reflist_L_fields: @@ -502,7 +390,7 @@ class SrcTemplate(object): if gedcomfield == GED_PAGE: self.__ged_page_reflist() else: - reflist = self.tempstruct[reftype] + reflist = self.structure[reftype] # reflist is typically a list like # [ ('', AUTHOR, '', ',', EMPTY, False, False, EMPTY, EMPTY, None, None), # ('', TITLE, '', ',', STYLE_QUOTE, False, False, EMPTY, EMPTY, None, None), @@ -643,4 +531,232 @@ class SrcTemplate(object): def page_gedcom(self, attr_list=None): if attr_list: self.set_attr_list(attr_list) - return self._reference(REF_TYPE_F, GED_PAGE) \ No newline at end of file + return self._reference(REF_TYPE_F, GED_PAGE) + +class TemplateElement(SecondaryObject): + """ + TemplateEelement class. + + This class is for keeping information about each template-element. + + TemplateElement: + + - template_element_name - English name of the element exactly as it appears + in Yates e.g. [WRITER FIRST] + + - name to be displayed in the user interface e.g. 'Name of the first + author' + + - hint e.g. "Doe, D.P. & Cameron, E." + + - tooltip e.g. "Give names in following form: 'FirstAuthorSurname, Given + Names & SecondAuthorSurname, Given Names'. Like this Gramps can parse the + name and shorten as needed." + + - list of Mappings - there would always be a GEDCOM mapping. Also we would + expect a CSL mapping + + """ + + def __init__(self, source=None): + """ + Create a new TemplateEelement instance, copying from the source if present. + """ + if source: + self.name = source.name + self.display = source.display + self.hint = source.hint + self.tooltip = source.tooltip + self.template_mapping_list = source.template_mapping_list + else: + self.name = "" + self.display = "" + self.hint = "" + self.tooltip = "" + self.template_mapping_list = [] + + def serialize(self): + """ + Convert the object to a serialized tuple of data. + """ + return (self.name, + self.display, + self.hint, + self.tooltip, +# [template_mapping.serialize() for template_mapping in self.template_mapping_list] + ) + + def to_struct(self): + """ + Convert the data held in this object to a structure (eg, + struct) that represents all the data elements. + + This method is used to recursively convert the object into a + self-documenting form that can easily be used for various + purposes, including diffs and queries. + + These structures may be primitive Python types (string, + integer, boolean, etc.) or complex Python types (lists, + tuples, or dicts). If the return type is a dict, then the keys + of the dict match the fieldname of the object. If the return + struct (or value of a dict key) is a list, then it is a list + of structs. Otherwise, the struct is just the value of the + attribute. + + :returns: Returns a struct containing the data of the object. + :rtype: dict + """ + return {"name": cuni(self.name), + "display": cuni(self.display), + "hint": cuni(self.hint), + "tooltip": cuni(self.tooltip), + } + + def unserialize(self, data): + """ + Convert a serialized tuple of data to an object. + """ + (self.name, self.type) = data + return self + + def get_name(self): + """ + Return the name for the Template element. + """ + return self.name + + def set_name(self, name): + """ + Set the name for the Template element according to the given argument. + """ + self.name = name + + def get_hint(self): + """ + Return the hint for the Template element. + """ + return self.hint + + def set_hint(self, hint): + """ + Set the hint for the Template element according to the given argument. + """ + self.hint = hint + + def get_display(self): + """ + Return the display form for the Template element. + """ + return self.display + + def set_display(self, display): + """ + Set the display form for the Template element according to the given + argument. + """ + self.display = display + + def get_tooltip(self): + """ + Return the tooltip for the Template element. + """ + return self.tooltip + + def set_tooltip(self, tooltip): + """ + Set the tooltip for the Template element according to the given argument. + """ + self.tooltip = tooltip + + def get_template_mapping_list(self): + return self.template_mapping_list + + def set_template_mapping_list(self, template_mapping_list): + self.template_mapping_list = template_mapping_list + + def add_template_mapping(self, template_mapping): + self.template_mapping_list.append(template_mapping) + +class MappingElement(SecondaryObject): + """ + TemplateEelement class. + + This class is for keeping information about how each [input] Template + Element is mapped to an Output form. + + Mapping: + + - Mapping_name - English name of the mapping. One mapping GEDCOM would + always be present. Other mappings are optional, but as we have decided + that (at least initially) we would use the CSL mapping, then this should + also be present). (The mappings should be the same as in the + MappingType). + + - map_target - the English interchange-element name onto which this + template-element is mapped. e.g. [WRITER FIRST] is mapped onto Recipient, + so this would contain recipient. + + - Mapping_order the sequence number for this mapping. So [WRITER LAST], + [WRITER FIRST] both map to 'Recipient', [WRITER FIRST] is 2, [WRITER + LAST] is 1 + + - separator - the separator after this element if there is another one + following, e.g.", " + """ + + def __init__(self, source=None): + """ + Create a new MappingEelement instance, copying from the source if + present. + """ + if source: + self.name = source.name + self.target = source.target + self.order = source.order + self.separator = source.separator + else: + self.name = "" + self.target = "" + self.order = "" + self.separator = "" + + def serialize(self): + """ + Convert the object to a serialized tuple of data. + """ + return (self.name, + self.target, + self.order, + self.separator + ) + + def unserialize(self, data): + """ + Convert a serialized tuple of data to an object. + """ + (self.name, self.type) = data + return self + + def get_name(self): + """ + Return the name for the mapping element. + """ + return self.name + + def set_name(self, name): + """ + Set the role according to the given argument. + """ + self.name = name + + def get_target(self): + """ + Return the tuple corresponding to the preset role. + """ + return self.target + + def set_target(self, target): + """ + Set the role according to the given argument. + """ + self.target = target diff --git a/gramps/gen/lib/srctemplatelist.py b/gramps/gen/lib/srctemplatelist.py new file mode 100644 index 000000000..5c0071a8e --- /dev/null +++ b/gramps/gen/lib/srctemplatelist.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2013 Tim G L Lyons +# +# 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$ + +""" +SrcTemplateList class for GRAMPS. +""" + +from __future__ import print_function + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------ +# +# Set up logging +# +#------------------------------------------------------------------------ +import logging +LOG = logging.getLogger('.template') + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- + +# Pattern from http://stackoverflow.com/questions/13789235/how-to-initialize-singleton-derived-object-once +class Singleton(type): + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + LOG.debug("**** Singleton metaclass initialised") + return cls._instances[cls] + +class SrcTemplateList(object): + """ + Manages a singleton list of the source templates. + + This should be replaced by access to the database when the templates are + stored in the database. + + I would expect the TemplateList object to contain a dictionary of Template + objects, with the template ID as a key. It needs a method to return a list + of keys, and another method to return the Template object for a given key. + In this way it would act like a database table. + """ + __metaclass__ = Singleton + def __init__(self): + self.template_list = {} + + def add_template(self, handle, template): + self.template_list[handle] = template + + def get_template_from_name(self, name): + # N.B. names can be ambiguous; it is better to use the handles which are + # guaranteed o be unique. This method returns the first matching + # template it finds. + + # Match processing in old set_template_key() + gedtempl = None + if name == 'UNKNOWN': + name = 'GEDCOM' + for template in self.template_list.itervalues(): + if template.get_name() == name: + return template + if template.get_name() == 'GEDCOM': + gedtempl = template + # Return the GEDCOM template if one was found + return gedtempl + + def get_template_from_handle(self, handle): + return self.template_list[handle] + + def get_template_list(self): + return self.template_list + + def template_defined(self, name): + if self.get_template_from_name(name) is None: + return False + else: + return True diff --git a/gramps/gui/editors/displaytabs/srctemplatetab.py b/gramps/gui/editors/displaytabs/srctemplatetab.py index 4e787eac8..32f1fabce 100644 --- a/gramps/gui/editors/displaytabs/srctemplatetab.py +++ b/gramps/gui/editors/displaytabs/srctemplatetab.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2013 Benny Malengier +# Copyright (C) 2013 Tim G L Lyons # # 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 @@ -43,7 +44,7 @@ from gi.repository import Gtk #------------------------------------------------------------------------- from gramps.gen.lib.srcattrtype import (SrcAttributeType, REF_TYPE_F, REF_TYPE_S, REF_TYPE_L, EMPTY) -from gramps.gen.lib import SrcAttribute, SrcTemplate +from gramps.gen.lib import SrcAttribute, SrcTemplate, SrcTemplateList from gramps.gen.plug.report.utils import get_address_ref_str from ...autocomp import StandardCustomSelector from ...widgets.srctemplatetreeview import SrcTemplateTreeView @@ -137,7 +138,7 @@ class SrcTemplateTab(GrampsTab): If title of the source is what we would set with autotitle, we set the checkbox to true. Otherwise to False """ - srctemp = SrcTemplate(self.src.get_template()) + srctemp = SrcTemplateList().get_template_from_name(self.src.get_template()) srctemp.set_attr_list(self.src.get_attribute_list()) title = srctemp.title_gedcom() if self.src.get_title() == title: @@ -204,9 +205,9 @@ class TemplateFields(object): """ show_default_cite_fields = False #we don't do this for now #obtain the template of the key - if SrcTemplate.template_defined(key): + if SrcTemplateList().template_defined(key): #a predefined template, - template = SrcTemplate.get_template(key) + template = SrcTemplateList().get_template_from_name(key).get_structure() else: return diff --git a/gramps/gui/editors/editsource.py b/gramps/gui/editors/editsource.py index 854247c9a..569301a95 100644 --- a/gramps/gui/editors/editsource.py +++ b/gramps/gui/editors/editsource.py @@ -3,7 +3,7 @@ # # Copyright (C) 2000-2006 Donald N. Allingham # Copyright (C) 2009 Gary Burton -# Copyright (C) 2011 Tim G L Lyons +# Copyright (C) 2011-2013 Tim G L Lyons # # 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 @@ -30,8 +30,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext import logging -log = logging.getLogger(".") -LOG = logging.getLogger(".citation") +LOG = logging.getLogger(".template") #------------------------------------------------------------------------- # @@ -45,7 +44,7 @@ from gi.repository import Gtk, Gdk # gramps modules # #------------------------------------------------------------------------- -from gramps.gen.lib import NoteType, Source, SrcTemplate, Citation +from gramps.gen.lib import NoteType, Source, SrcTemplate, Citation, SrcTemplateList from gramps.gen.db import DbTxn from gramps.gen.utils.file import media_path_full from ..thumbnails import get_thumbnail_image @@ -69,6 +68,7 @@ from ..glade import Glade # #------------------------------------------------------------------------- +FIRST = True class EditSource(EditPrimary): def __init__(self, dbstate, uistate, track, source, citation=None, @@ -83,6 +83,14 @@ class EditSource(EditPrimary): must handle this (corresponds to closing the editor with nothing made!) """ + # FIXME: Is there a cleaner place to initially load the template data? + global FIRST + if FIRST: + LOG.debug("**** load csv data") + from gramps.plugins.srctemplates.importcsv import load_srctemplates_data + load_srctemplates_data() + LOG.debug("**** csv data loaded\n\n") + FIRST = False self.srctemp = None self.citation = citation self.template_tab = None @@ -350,10 +358,16 @@ class EditSource(EditPrimary): """ #we only construct once the template to use to format information if self.srctemp is None: - self.srctemp = SrcTemplate(self.obj.get_template()) - #if source template changed, reinit template - if self.obj.get_template() != self.srctemp.get_template_key(): - self.srctemp.set_template_key(self.obj.get_template()) + self.srctemp = SrcTemplateList().get_template_from_name(self.obj.get_template()) + # FIXME: I am not sure what the code below was doing. The SrcTemplate + # had been set from the name in the Src record, then a check was made as + # to whether the old name from the Src record was the same as the name + # of the template. But since the template was found from its name, this + # must be the case. + +# #if source template changed, reinit template +# if self.obj.get_template() != self.srctemp.get_template_key(): +# self.srctemp.set_template_key(self.obj.get_template()) #set new attrlist in template if self.citation_loaded: citeattr = self.citation.get_attribute_list() diff --git a/gramps/gui/widgets/srctemplatetreeview.py b/gramps/gui/widgets/srctemplatetreeview.py index 7b9ee5490..08247bac4 100644 --- a/gramps/gui/widgets/srctemplatetreeview.py +++ b/gramps/gui/widgets/srctemplatetreeview.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2013 Benny Malengier +# Copyright (C) 2013 Tim G L Lyons # # 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 @@ -44,7 +45,7 @@ from gi.repository import Gtk # Gramps classes # #------------------------------------------------------------------------- -from gramps.gen.lib import SrcTemplate +from gramps.gen.lib import SrcTemplate, SrcTemplateList #------------------------------------------------------------------------- # @@ -81,7 +82,10 @@ class SrcTemplateTreeView(Gtk.TreeView): self.Key2Path = {} # store (key, src_type) self.model = Gtk.TreeStore(str, str) - alltexts = sorted((SrcTemplate.template_description(x), x) for x in SrcTemplate.all_templates()) + tlist = SrcTemplateList() + alltexts = sorted((tlist.get_template_from_handle(handle).get_descr(), + tlist.get_template_from_handle(handle).get_name()) + for handle in tlist.get_template_list() ) parentiter = None parentiterlev1 = None prevstrval = ['', ''] diff --git a/gramps/plugins/srctemplates/importcsv.py b/gramps/plugins/srctemplates/importcsv.py new file mode 100644 index 000000000..968d6b5e7 --- /dev/null +++ b/gramps/plugins/srctemplates/importcsv.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2013 Benny Malengier +# Copyright (C) 2013 Tim G L Lyons +# +# 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$ + +""" +Import SrcTemplates for GRAMPS. +""" + +from __future__ import print_function + +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +import csv + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from gramps.gen.utils.id import create_id +from gramps.gen.lib.srcattrtype import * +from gramps.gen.lib.date import Date +from gramps.gen.lib.srctemplate import SrcTemplate, TemplateElement +from gramps.gen.lib.srctemplatelist import SrcTemplateList + +#------------------------------------------------------------------------ +# +# Set up logging +# +#------------------------------------------------------------------------ +import logging +LOG = logging.getLogger('.template') + +#columns in a csv file defining templates +NRCOL = 0 +CATCOL = 1 +CATTYPECOL = 2 +TYPECOL = 3 +DESCRCOL= 4 +CITETYPECOL = 5 +IDENTCOL = 6 +LDELCOL = 7 # left delimiter +FIELDCOL = 8 +LABELCOL = 9 +RDELCOL = 10 # right delimiter +GEDCOMCOL = 11 +SHORTERCOL = 12 +STYLECOL = 13 +PRIVACYCOL = 14 +OPTCOL = 15 +HINTCOL = 16 +TOOLTIPCOL = 17 + +UNKNOWN = 'UNKNOWN' +DESCR = -10 + +# the GEDCOM type is predefined and always present. Other templates will be +# loaded via plugins +TEMPLATES = { + 'GEDCOM': { + REF_TYPE_L: [ + ('', SrcAttributeType.AUTHOR, _(''), '.', EMPTY, False, False, EMPTY, GED_AUTHOR, + None, None), + ('', SrcAttributeType.TITLE, _(''), '.', STYLE_QUOTE, False, False, EMPTY, GED_TITLE, + None, None), + ('', SrcAttributeType.PUB_INFO, _(''), '', EMPTY, False, False, EMPTY, GED_PUBINF, + None, None), + ], + REF_TYPE_F: [ + ('', SrcAttributeType.AUTHOR, _(''), ',', EMPTY, False, False, EMPTY, EMPTY, + None, None), + ('', SrcAttributeType.TITLE, _(''), ',', STYLE_QUOTE, False, False, EMPTY, EMPTY, + None, None), + ('', SrcAttributeType.PUB_INFO, _(''), '.', EMPTY, False, False, EMPTY, EMPTY, + None, None), + ('', SrcAttributeType.DATE, _(''), ' -', EMPTY, False, False, EMPTY, EMPTY, + None, None), + ('', SrcAttributeType.PAGE, _('Page(s)'), '.', EMPTY, False, False, EMPTY, EMPTY, + None, None), + ], + REF_TYPE_S: [ + ('', SrcAttributeType.AUTHOR, _(''), ',', EMPTY, False, False, EMPTY, EMPTY, + None, None), + ('', SrcAttributeType.DATE, _(''), ' -', EMPTY, False, False, EMPTY, EMPTY, + None, None), + ('', SrcAttributeType.PAGE, _('Page(s)'), '.', EMPTY, False, False, EMPTY, EMPTY, + None, None), + ], + DESCR: '%(first)s - %(sec)s - %(third)s' % { 'first': _('Basic'), 'sec': _('GEDCOM Style'), 'third': _('')}, + }, + UNKNOWN: { + REF_TYPE_L: [ + ], + REF_TYPE_F: [ + ], + REF_TYPE_S: [ + ], + DESCR: _("Unrecognized Template. Download it's definition."), + }, + } + +def load_srctemplates_data(): + """ + Loads the srctemplates defined, and returns a dict with template data + """ + from gramps.gen.plug import BasePluginManager + bpmgr = BasePluginManager.get_instance() + pdatas = bpmgr.get_reg_srctemplates() + templatemap = {} + + for plugin in pdatas: + mod = bpmgr.load_plugin(plugin) + if mod: + csvfilename = mod.csvfile + with open(csvfilename, 'rb') as csvfile: + templatemap.update(load_srctemplate_csv(csvfile)) + return templatemap + +def load_srctemplate_csv(csvfile): + """ + Loads a template csvfile, and returns a dict with template data + Note: csvfile could be a list containing strings! + """ + LOG.debug("**** importcsv.load_srctemplate_cvs called") + first = True + TYPE2CITEMAP = {} + TYPE2TEMPLATEMAP = {} + tlist = SrcTemplateList() + CITE_TYPES = {'F': REF_TYPE_F, 'L': REF_TYPE_L, 'S': REF_TYPE_S} + GEDCOMFIELDS = {'A': GED_AUTHOR, 'T': GED_TITLE, + 'P': GED_PUBINF, 'D': GED_DATE} + SHORTERALG = {'LOC': SHORTERALG_LOC, 'YEAR': SHORTERALG_YEAR, + 'ETAL': SHORTERALG_ETAL, 'REV.': SHORTERALG_REVERT_TO_DOT} + STYLES = {'Quoted': STYLE_QUOTE, 'Italics': STYLE_EMPH, + 'QuotedCont': STYLE_QUOTECONT, 'Bold': STYLE_BOLD} + + template = SrcTemplate() + template.set_name('GEDCOM') + template.set_descr('%(first)s - %(sec)s - %(third)s' % { 'first': _('Basic'), 'sec': _('GEDCOM Style'), 'third': _('')}) + handle = create_id() + template.set_handle(handle) + TYPE2TEMPLATEMAP['GEDCOM'] = template + tlist = SrcTemplateList() + tlist.add_template(handle, template) + + for (cite_type, slist) in TEMPLATES['GEDCOM'].iteritems(): + if cite_type != DESCR: + for struct in slist: + LOG.debug(struct) + ldel = struct[0] + field_type = struct[1] + field_label = struct[2] + rdel = struct[3] + style = struct[4] + private = struct[5] + optional = struct[6] + shorteralg = struct[7] + gedcommap = struct[8] + hint = struct[9] + tooltip = struct[10] + te = TemplateElement() + te.set_name(field_type) + te.set_display(field_label) + te.set_hint(hint or SrcAttributeType.get_default_hint(field_type)) + te.set_tooltip(tooltip or SrcAttributeType.get_default_tooltip(field_type)) + template.add_template_element(te) + template.add_structure_element(cite_type, [(ldel, field_type, + field_label, rdel, style, private, optional, + shorteralg, gedcommap, hint, tooltip)]) + + template = SrcTemplate() + template.set_name(UNKNOWN) + template.set_descr(_("Unrecognized Template. Download it's definition.")) + handle = create_id() + template.set_handle(handle) + TYPE2TEMPLATEMAP[UNKNOWN] = template + tlist = SrcTemplateList() + tlist.add_template(handle, template) + + for cite_type in (REF_TYPE_F, REF_TYPE_L, REF_TYPE_S): + template.add_structure_element(cite_type, []) + + reader = csv.reader(csvfile, delimiter=';') + + prevtempl = '' + newtempl = True + for row in reader: + if first: + #skip first row with headers + first=False + continue + + if row[CATCOL]: + cat = row[CATCOL].strip() + cattype = row[CATTYPECOL].strip() + types = row[TYPECOL].strip() + descr = row[DESCRCOL].strip() + source_type = row[IDENTCOL].strip() + if prevtempl != source_type: + newtempl = True + prevtempl = source_type + else: + newtempl = False + if descr: + source_descr = '%s - %s - %s (%s)' % (_(cat), _(cattype), + _(types), _(descr)) + else: + source_descr = '%s - %s - %s' % (_(cat), _(cattype), _(types)) + if source_type in TYPE2TEMPLATEMAP: + if not TYPE2TEMPLATEMAP[source_type].get_descr() == source_descr: + raise NotImplementedError, source_type + ' ' + TYPE2TEMPLATEMAP[source_type].get_descr() + ' NOT equal to known description' + source_descr + if newtempl: + #the template is new in this csv, but already defined, probably user error + raise NotImplementedError, 'Source template ' + prevtempl + ' is twice defined in the csv.' + else: + template = SrcTemplate() + template.set_name(source_type) + template.set_descr(source_descr) + handle = create_id() + template.set_handle(handle) + TYPE2TEMPLATEMAP[source_type] = template + tlist = SrcTemplateList() + tlist.add_template(handle, template) +# TYPE2CITEMAP[source_type] = {REF_TYPE_L: [], REF_TYPE_F: [], +# REF_TYPE_S: [], DESCR: source_descr} + + if row[CITETYPECOL]: + #new citation type, + cite_type = row[CITETYPECOL].strip() + assert cite_type in ['F', 'L', 'S'], str(cite_type) + if cite_type == 'S': + shortcite = True + else: + shortcite = False + cite_type = CITE_TYPES[cite_type] + #add field for template to evidence style + field = row[FIELDCOL].strip() + field_type = field.replace('[', '').replace(']','').lower().capitalize() + #field_type = field.replace(' ', '_').replace("'","")\ + # .replace('&','AND').replace('(', '6').replace(')','9')\ + # .replace('[', '').replace(']','').replace('/', '_OR_')\ + # .replace(',', '').replace('.', '').replace(':', '')\ + # .replace('-', '_') + + #we need to force English SrcAttributeType + ifield_type = SrcAttributeType() + ifield_type.set_from_xml_str(field_type) + ifield_type = int(SrcAttributeType(ifield_type)) + if ifield_type == SrcAttributeType.CUSTOM: + raise NotImplementedError, "field must be a known SrcAttributeType, is " + str(SrcAttributeType(field_type)) + field_type = ifield_type + #field_descr = field.replace('[', '').replace(']','').lower().capitalize() + field_label = row[LABELCOL].strip() + #field_label = field_label.replace("'", "\\'") + private = False + if row[PRIVACYCOL].strip(): + private = True + optional = False + if row[OPTCOL].strip(): + optional = True + shorteralg = SHORTERALG.get(row[SHORTERCOL].strip()) or EMPTY + gedcommap = GEDCOMFIELDS.get(row[GEDCOMCOL].strip()) or EMPTY + style = STYLES.get(row[STYLECOL].strip()) or EMPTY + hint = row[HINTCOL] + tooltip = row[TOOLTIPCOL] + te = TemplateElement() + te.set_name(field_type) + te.set_display(_(field_label)) + te.set_hint(_(hint) or SrcAttributeType.get_default_hint(field_type)) + te.set_tooltip(_(tooltip) or SrcAttributeType.get_default_tooltip(field_type)) + + if source_type in TYPE2TEMPLATEMAP: + template = TYPE2TEMPLATEMAP[source_type] + else: + template = SrcTemplate() + handle = create_id() + template.set_handle(handle) + TYPE2TEMPLATEMAP[source_type] = template + tlist.add_template(handle, template) + # FIXME: If the template element is already present, don't add it again + template.add_template_element(te) + template.add_structure_element(cite_type, [(row[LDELCOL], field_type, + _(field_label), row[RDELCOL], style, private, optional, + shorteralg, gedcommap, _(hint), _(tooltip))]) + + LOG.debug(tlist.get_template_list()) + for handle in tlist.get_template_list(): + LOG.debug(tlist.get_template_from_handle(handle).to_struct()) + return TYPE2CITEMAP