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)