Source code for mcot.bibtex.file
"""A list of bibtex entry stored in a .bib file
"""
from . import entry
import copy
import os
[docs]class BibTexSet(object):
[docs] def __init__(self, entries):
self.entries = {}
for entry in entries:
self.add(entry)
[docs] def add(self, entry, adjust_key=False):
if entry in self:
for alternative in self:
if alternative == entry:
print('entry %s already exists as %s' % (entry, alternative))
print('removing %s' % entry)
return
if entry.key in self.keys():
if not adjust_key:
raise KeyError("Entry with key %s already found:\nNew:\n%r\nOld:\n%r" % (entry.key, entry, self[entry.key]))
entry.adjust_id(self)
self[entry.key] = entry
[docs] def remove(self, entry):
bad_entries = []
for alternative in self:
if alternative == entry:
bad_entries.append(alternative.key)
for key in bad_entries:
del self[key]
def __setitem__(self, key, value):
if value.key != key:
raise ValueError("Storing BibTexEntry %s under the wrong key %s" % (value, key))
self.entries[key] = value
def __getitem__(self, item):
return self.entries[item]
def __delitem__(self, key):
del self.entries[key]
def __iter__(self):
yield from self.entries.values()
def __contains__(self, item):
for entry in self:
if item == entry:
return True
return False
def __len__(self):
return len(self.entries)
def __str__(self):
return "%i Bibtex entries" % len(self)
def __repr__(self):
return '\n\n'.join(repr(entry) for entry in self)
[docs] def write(self, filename):
with open(filename, 'w', encoding='utf-8') as outfile:
outfile.write(repr(self))
outfile.write('\n')
[docs] def keys(self):
return self.entries.keys()
def __add__(self, other):
res = copy.copy(self)
for entry.key in other.keys():
res.add(other, adjust_key=True)
[docs] @staticmethod
def from_string(text: str):
entries = []
open_braces = 0
for idx_line, line in enumerate(text.splitlines()):
if len(line.strip()) == 0:
continue
if line[0] == '@':
if open_braces != 0:
raise ValueError('New Bibtex entry starts on line %i (%s), but previous bibtex entry %s did not end' % (idx_line, line, current_entry))
end_type = line.find('{')
if end_type == -1:
raise IOError('Line %i (%s) does not start a valid BibTex entry: No { found' % (idx_line, line))
end_key = line.find(',', end_type)
if end_key == -1:
if line[-1] == '}':
continue
raise IOError('Line %i (%s) does not start a valid BibTex entry: No , found' % (idx_line, line))
current_entry = entry.BibTexEntry(type=line[1:end_type].strip(),
key=line[1 + end_type:end_key].strip(), check=False)
open_braces += 1
elif open_braces == 1:
# looking for new tags
idx_eq = line.find('=')
if idx_eq != -1:
field_name = line[:idx_eq].strip()
field_text = line[idx_eq + 1:].strip()
if check_field_text_finished(field_text):
short_text, stop_entry = finish_text(field_text)
current_entry[field_name] = short_text
open_braces = 1
if stop_entry:
entries.append(current_entry)
open_braces = 0
else:
open_braces = 2
elif line.strip()[0] == '}':
open_braces -= 1
entries.append(current_entry)
elif len(line.strip()) != 0:
raise IOError("Unrecognized line %i (%s) in bibtex entry %s" % (idx_line, line, current_entry))
elif open_braces >= 2:
# Continue reading for an existing tag
field_text = field_text + '\n' + line.strip()
if check_field_text_finished(field_text):
short_text, stop_entry = finish_text(field_text)
current_entry[field_name] = short_text
open_braces = 1
if stop_entry:
entries.append(current_entry)
open_braces = 0
return BibTexSet(entries)
[docs]def check_field_text_finished(text):
open_braces = 0
ignore_next = False
for char in text:
if ignore_next:
ignore_next = False
else:
if char == '{':
open_braces += 1
elif char == '}':
open_braces -= 1
if open_braces < 0:
open_braces = 0
elif char == '\\':
ignore_next = True
return open_braces == 0
[docs]def finish_text(text):
if text[-1] == ',':
text = text[:-1].strip()
if len(text) > 1 and text[0] == '"' and text[-1] == '"':
text = text[1:-1].strip()
if len(text) > 1 and text[0] == '{' and text[-1] == '}':
text = text[1:-1].strip()
finished = check_field_text_finished('{' + text)
if finished:
text = text[:text.rfind('}')]
return text, finished
[docs]class BibTexFile(BibTexSet):
[docs] def __init__(self, filename):
self.filename = filename
self.entries = {}
if not os.path.isfile(filename):
print('creating new bibtex file: %s' % filename)
return
with open(filename, 'r', encoding='utf-8') as infile:
text = infile.read()
entries = self.from_string(text)
super().__init__(entries)
[docs] def add_to_file(self, entry, adjust_key=False):
if entry in self:
return
self.add(entry, adjust_key=adjust_key)
with open(self.filename, 'a', encoding='utf-8') as outfile:
outfile.write('\n%r\n' % entry)