# Copyright (c) 2006-2019 Andrey Golovigin # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """parse LaTeX aux file """ from __future__ import unicode_literals, with_statement import re import pybtex.io from pybtex.errors import report_error from pybtex.exceptions import PybtexError from pybtex import py3compat @py3compat.python_2_unicode_compatible class AuxDataError(PybtexError): def __init__(self, message, context=None): super(AuxDataError, self).__init__(message, context.filename) self.context = context def get_context(self): if self.context.line: marker = '^' * len(self.context.line) return self.context.line + '\n' + marker def __str__(self): base_message = py3compat.__str__(super(AuxDataError, self)) lineno = self.context.lineno location = 'in line {0}: '.format(lineno) if lineno else '' return location + base_message class AuxDataContext(object): lineno = None line = None filename = None def __init__(self, filename): self.filename = filename class AuxData(object): command_re = re.compile(r'\\(citation|bibdata|bibstyle|@input){(.*)}') context = None style = None data = None citations = None def __init__(self, encoding): self.encoding = encoding self.citations = [] self._canonical_keys = {} def handle_citation(self, keys): for key in keys.split(','): key_lower = key.lower() if key_lower in self._canonical_keys: existing_key = self._canonical_keys[key_lower] if key != existing_key: msg = 'case mismatch error between cite keys {0} and {1}' report_error(AuxDataError(msg.format(key, existing_key), self.context)) self.citations.append(key) self._canonical_keys[key_lower] = key def handle_bibstyle(self, style): if self.style is not None: report_error(AuxDataError(r'illegal, another \bibstyle command', self.context)) else: self.style = style def handle_bibdata(self, bibdata): if self.data is not None: report_error(AuxDataError(r'illegal, another \bibdata command', self.context)) else: self.data = bibdata.split(',') def handle_input(self, filename): self.parse_file(filename, toplevel=False) def handle_command(self, command, value): action = getattr(self, 'handle_%s' % command.lstrip('@')) action(value) def parse_line(self, line, lineno): self.context.lineno = lineno self.context.line = line.strip() match = self.command_re.match(line) if match: command, value = match.groups() self.handle_command(command, value) def parse_file(self, filename, toplevel=True): previous_context = self.context self.context = AuxDataContext(filename) with pybtex.io.open_unicode(filename, encoding=self.encoding) as aux_file: for lineno, line in enumerate(aux_file, 1): self.parse_line(line, lineno) if previous_context: self.context = previous_context else: self.context.line = None self.context.lineno = None # these errors are fatal - always raise an exception instead of using # erorrs.report_error() if toplevel and self.data is None: raise AuxDataError(r'found no \bibdata command', self.context) if toplevel and self.style is None: raise AuxDataError(r'found no \bibstyle command', self.context) def parse_file(filename, encoding=None): """Parse a file and return an AuxData object.""" data = AuxData(encoding) data.parse_file(filename) return data