278 lines
10 KiB
Python
278 lines
10 KiB
Python
#
|
|
# 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
|
|
|
|
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 = []
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
def get_citation_count(self):
|
|
"""
|
|
Report the number of citations in this bibliography.
|
|
|
|
:return: number of citations
|
|
:rtype: int
|
|
"""
|
|
return len(self.__citation_list)
|
|
|
|
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
|