"""yamref ... BibTeX references for yamdb (Yet Another Materials Database)."""
import os
from importlib import resources as ir
import bibtexparser
from bibtexparser.customization import convert_to_unicode
from bibtexparser.bparser import BibTexParser
from yamdb.yamdb import get_file_digest
_db_cache = {}
[docs]
class BibTexDB:
"""Provide the content of a BibTeX file to resolve references.
Parameters
----------
fname : str
Filename of the BibTeX database file.
"""
# this handling of formatting needs minimal effort but relies on
# order preservation of python dicts, which is standard only from 3.7 on
#
# http://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/
#
# "However, this situation is changing. Standard dict objects
# preserve order in the reference (CPython) implementations of
# Python 3.5 and 3.6, and this order-preserving property is
# becoming a language feature in Python 3.7."
#
# _formats could as well be read from a yaml file
#
_formats = {
'article': {
'author': "%s ",
'year': "(%s) ",
'title': "%s. ",
'journal': "%s, ",
'volume': "%s, ",
'pages': "%s.",
},
'book': {
'author': "%s ",
'year': "(%s) ",
'title': "%s. ",
'publisher': "%s. ",
'address': "%s.",
},
'techreport': {
'author': "%s ",
'year': "(%s) ",
'title': "%s. ",
'number': "%s, ",
'institution': "%s.",
},
'phdthesis': {
'author': "%s ",
'year': "(%s) ",
'title': "%s. ",
'school': "PhD Thesis %s.",
},
'misc': {
'author': "%s ",
'year': "(%s) ",
'title': "%s. ",
},
'incollection': {
'author': "%s ",
'year': "(%s) ",
'title': "%s. ",
'booktitle': "In: %s. ",
'volume': "%s, ",
'pages': "%s.",
}
}
def __init__(self, fname):
"""Load a BibTeX database file.
Parameters
----------
fname : str
Filename of the BibTeX database file.
"""
self._load_database(fname)
def _format(self, refdict, field):
"""Format a field of a BibTeX entry for later use in the reference string.
Parameters
----------
refdict : dictionary
Entry/record in the BibTeX database corresponding to a cite key.
field : str
Field of the entry/record to be formatted, e.g. 'author'.
Returns
-------
str or None
Formatted field of the entry/record.
See Also
--------
bibtexparser
"""
type_ = refdict['ENTRYTYPE']
if field in self._formats[type_].keys():
fieldstr = self._formats[type_][field] % refdict[field].rstrip('.')
else:
fieldstr = None
return fieldstr
[docs]
def get_unformatted_entry(self, key):
"""Get the unformatted BibTeX entry (dict) for cite key.
Parameters
----------
key : str
Citation key of the entry, e.g 'IidaGuthrie1988'.
Returns
-------
dict or None
Unformatted entry/record corresponding to the BibTeX entry.
See Also
--------
bibtexparser
"""
if key in self.references.entries_dict.keys():
return self.references.entries_dict[key]
else:
return None
def _load_database(self, fname):
"""Parse BibTeX file into corresponding dictionary.
Parameters
----------
fname : str
Name of the BibTeX file to be parsed, e.g. 'references.bib'.
See Also
--------
bibtexparser
"""
with open(fname) as bibtex_file:
parser = BibTexParser()
parser.customization = convert_to_unicode
self.references = bibtexparser.load(bibtex_file, parser=parser)
[docs]
def get_entry(self, key):
"""Get the formatted BibTeX entry (string) for cite key.
Parameters
----------
key : str
Citation key of the entry, e.g 'IidaGuthrie1988'.
Returns
-------
str or None
Formatted entry/record belonging to the cite key.
See Also
--------
bibtexparser
"""
refdict = self.get_unformatted_entry(key)
if refdict is not None:
entry = ""
for field in self._formats[refdict['ENTRYTYPE']].keys():
fieldstr = None
if field in refdict.keys():
fieldstr = self._format(refdict, field)
# this is for books with editor but without author
elif field == 'author':
if 'editor' in refdict.keys():
refdict['author'] = refdict['editor']
fieldstr = self._format(refdict, field)
if fieldstr is not None:
entry += fieldstr
else:
entry = None
return entry
[docs]
def get_source_filename(self, key):
"""Get the filename of the pdf containing the cited paper.
**Not meaningful for general use.**
Parameters
----------
key : str
Citation key of the entry, e.g 'IidaGuthrie1988'.
Returns
-------
str or None
Filename of the cited paper.
"""
fname = None
refdict = self.get_unformatted_entry(key)
if refdict is not None:
if 'file' in refdict.keys():
fname = refdict['file']
return fname
[docs]
def get_references_from_db(db_file, key):
"""Get reference for key from db_file.
Parameters
----------
db_file : str
Name of the reference database BibTeX file, e.g. 'references.bib'
key : str
Citation key of the reference, e.g. 'IidaGuthrie1988'.
Returns
-------
str
Reference corresponding to the supplied citation key.
"""
hash = get_file_digest(db_file)
if hash in _db_cache.keys():
db = _db_cache[hash]
else:
db = BibTexDB(db_file)
_db_cache[hash] = db
return db.get_entry(key)
def _get_references_from_included_db(db_file, key):
"""Get reference for key from db_file supplied in yamdb/data.
Parameters
----------
db_file : str
Name of the reference database BibTeX file, typically 'references.bib'
key : str
Citation key of the reference, e.g. 'IidaGuthrie1988'.
Returns
-------
str
Reference corresponding to the supplied citation key.
"""
fname = os.path.join(ir.files('yamdb'), 'data', db_file)
return get_references_from_db(fname, key)
[docs]
def get_from_references(key):
"""Get reference for key from the references.bib file included in yamdb/data.
Parameters
----------
key : str
Citation key of the reference, e.g. 'IidaGuthrie1988'.
Returns
-------
str
Reference corresponding to the supplied citation key.
"""
return _get_references_from_included_db('references.bib', key)