#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 Brian G. Matherly
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
Contain and organize bibliographic information.
"""
import string
import math
from ...lib.citation import Citation as lib_Citation
[docs]class Citation(object):
"""
Store information about a citation and all of its references.
"""
def __init__(self):
"""
Initialize members.
"""
self.__src_handle = None
self.__ref_list = []
[docs] def get_source_handle(self):
"""
Provide the handle to the source that this citation is for.
:return: Source Handle
:rtype: handle
"""
return self.__src_handle
[docs] def set_source_handle(self, handle):
"""
Set the handle for the source that this citation is for.
:param handle: Source Handle
:type handle: handle
"""
self.__src_handle = handle
[docs] def get_ref_list(self):
"""
List all the references to this citation.
:return: a list of references
:rtype: list of :class:`~.citation.Citation` objects
"""
return self.__ref_list
[docs] def add_reference(self, source_ref):
"""
Add a reference to this citation. If a similar reference exists, don't
add another one.
:param source_ref: Source Reference
:type source_ref: :class:`~.citation.Citation`
:return: The key of the added reference among all the references.
:rtype: char
"""
letter_count = len(string.ascii_lowercase)
ref_count = len(self.__ref_list)
x_ref_count = ref_count
# Return "a" for ref_count = 0, otherwise log(0) does not work
if ref_count == 0:
self.__ref_list.append(("a", source_ref))
return "a"
last_letter = string.ascii_lowercase[ ref_count % letter_count ]
key = ""
# Calculate prek number of digits.
number_of_letters = int(math.log(float(ref_count), float(letter_count)))+1
# Exclude index for number_of_letters-1
for n in range(1, number_of_letters-1):
ref_count -= pow(letter_count, n)
# Adjust number_of_letters for new index
number_of_letters = int(math.log(float(ref_count), float(letter_count))) +1
for n in range(1, number_of_letters):
x_ref_count -= pow(letter_count, n)
for letter in range(1, number_of_letters):
index = x_ref_count / pow(letter_count, letter) % letter_count
key += string.ascii_lowercase[ index ]
key = key + last_letter
self.__ref_list.append((key, source_ref))
return key
[docs]class Bibliography(object):
"""
Store and organize multiple citations into a bibliography.
"""
MODE_DATE = 2**0
MODE_PAGE = 2**1
MODE_CONF = 2**2
MODE_NOTE = 2**3
MODE_MEDIA = 2**4
MODE_ALL = MODE_DATE | MODE_PAGE | MODE_CONF | MODE_NOTE | MODE_MEDIA
def __init__(self, mode=MODE_ALL):
"""
A bibliography will store citations (sources) and references to those
citations (citations). Duplicate entries will not be added. To change
what is considered duplicate, you can tell the bibliography what source
ref information you are interested in by passing in the mode.
Possible modes include:
- MODE_DATE
- MODE_PAGE
- MODE_CONF
- MODE_NOTE
- MODE_MEDIA
- MODE_ALL
If you only care about pages, set "mode=MODE_PAGE".
If you only care about dates and pages, set "mode=MODE_DATE|MODE_PAGE".
If you care about everything, set "mode=MODE_ALL".
"""
self.__citation_list = []
self.mode = mode
[docs] def add_reference(self, lib_citation):
"""
Add a reference to a source to this bibliography. If the source already
exists, don't add it again. If a similar reference exists, don't
add another one.
:param citation: Citation object
:type citation: :class:`~.citation.Citation`
:return: A tuple containing the index of the source among all the
sources and the key of the reference among all the references.
If there is no reference information, the second element will
be None.
:rtype: (int,char) or (int,None)
.. note::
Within this file, the name 'citation' is used both for
gen.lib.Citation, and for _bibliography.Citation. It is not clear
how best to rename the concepts in this file to avoid the clash,
so the names have been retained. In this function, lib_citation
is used for gen.lib.Citation instances, and citation for
_bibliography.Citation instances. Elsewhere in this file,
source_ref is used for gen.lib.Citation instances.
"""
source_handle = lib_citation.get_reference_handle()
cindex = 0
rkey = ""
citation = None
citation_found = False
for citation in self.__citation_list:
if citation.get_source_handle() == source_handle:
citation_found = True
break
cindex += 1
if not citation_found:
citation = Citation()
citation.set_source_handle(source_handle)
cindex = len(self.__citation_list)
self.__citation_list.append(citation)
if self.__sref_has_info(lib_citation):
for key, ref in citation.get_ref_list():
if self.__srefs_are_equal(ref, lib_citation):
# if a reference like this already exists, don't add
# another one
return (cindex, key)
rkey = citation.add_reference(lib_citation)
return (cindex, rkey)
[docs] def get_citation_count(self):
"""
Report the number of citations in this bibliography.
:return: number of citations
:rtype: int
"""
return len(self.__citation_list)
[docs] def get_citation_list(self):
"""
Return a list containing all the citations in this bibliography.
:return: citation list
:rtype: list of :class:`Citation` objects
"""
return self.__citation_list
def __sref_has_info(self, source_ref):
"""
Determine if this source_ref has any useful information based on the
current mode.
"""
if ( self.mode & self.MODE_PAGE ) == self.MODE_PAGE:
if source_ref.get_page() != "":
return True
if ( self.mode & self.MODE_DATE ) == self.MODE_DATE:
date = source_ref.get_date_object()
if date is not None and not date.is_empty():
return True
if ( self.mode & self.MODE_CONF ) == self.MODE_CONF:
confidence = source_ref.get_confidence_level()
if confidence is not None and confidence != \
lib_Citation.CONF_NORMAL:
return True
if ( self.mode & self.MODE_NOTE ) == self.MODE_NOTE:
if len(source_ref.get_note_list()) != 0:
return True
if ( self.mode & self.MODE_MEDIA ) == self.MODE_MEDIA:
if len(source_ref.get_media_list()) != 0:
return True
# Can't find anything interesting.
return False
def __srefs_are_equal(self, source_ref1, source_ref2):
"""
Determine if two source references are equal based on the
current mode.
"""
# The criterion for equality (in mode==MODE_ALL) is changed for
# citations. Previously, it was based on is_equal from SecondaryObject,
# which does a 'cmp' on the serialised data. (Note that this might not
# have worked properly for Dates; see comments in Date.is_equal and
# EditCitation.data_has_changed). The comparison is now made as to
# whether the two gen.lib.Citations have the same handle (i.e. they are
# actually the same database objects). It is felt that this better
# reflects the intent of Citation objects, which can be merged if they
# are intended to represent the same citation.
if self.mode == self.MODE_ALL:
return source_ref1.handle == source_ref2.handle
if ( self.mode & self.MODE_PAGE ) == self.MODE_PAGE:
if source_ref1.get_page() != source_ref2.get_page():
return False
if ( self.mode & self.MODE_DATE ) == self.MODE_DATE:
date1 = source_ref1.get_date_object()
date2 = source_ref2.get_date_object()
if not date1.is_equal(date2):
return False
if ( self.mode & self.MODE_CONF ) == self.MODE_CONF:
conf1 = source_ref1.get_confidence_level()
conf2 = source_ref2.get_confidence_level()
if conf1 != conf2:
return False
if ( self.mode & self.MODE_NOTE ) == self.MODE_NOTE:
nl1 = source_ref1.get_note_list()
nl2 = source_ref2.get_note_list()
if len(nl1) != len(nl2):
return False
for notehandle in nl1:
if notehandle not in nl2:
return False
if ( self.mode & self.MODE_MEDIA ) == self.MODE_MEDIA:
nl1 = source_ref1.get_media_list()
nl2 = source_ref2.get_media_list()
if len(nl1) != len(nl2):
return False
for mediahandle in nl1:
if mediahandle not in nl2:
return False
# Can't find anything different. They must be equal.
return True