You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
4.8 KiB
139 lines
4.8 KiB
# 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
|
|
|