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.
325 lines
10 KiB
325 lines
10 KiB
# Copyright (c) 2006-2021 Andrey Golovizin
|
|
#
|
|
# 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.
|
|
|
|
from __future__ import print_function, unicode_literals
|
|
|
|
from collections import defaultdict
|
|
|
|
import six
|
|
from pybtex.bibtex.builtins import builtins, print_warning
|
|
from pybtex.bibtex.exceptions import BibTeXError
|
|
from pybtex.bibtex.utils import wrap
|
|
from pybtex.utils import CaseInsensitiveDict
|
|
|
|
|
|
#from pybtex.database.input import bibtex
|
|
|
|
|
|
class Variable(object):
|
|
|
|
def _undefined(self):
|
|
raise NotImplementedError
|
|
|
|
default = property(_undefined)
|
|
value_type = property(_undefined)
|
|
|
|
def __init__(self, value=None):
|
|
self.set(value)
|
|
|
|
def set(self, value):
|
|
if value is None:
|
|
value = self.default
|
|
self.validate(value)
|
|
self._value = value
|
|
def validate(self, value):
|
|
if not (isinstance(value, self.value_type) or value is None):
|
|
raise ValueError('Invalid value for BibTeX %s: %s' % (self.__class__.__name__, value))
|
|
def execute(self, interpreter):
|
|
interpreter.push(self.value())
|
|
def value(self):
|
|
return self._value
|
|
|
|
def __repr__(self):
|
|
return u'{0}({1})'.format(type(self).__name__, repr(self.value()))
|
|
|
|
def __eq__(self, other):
|
|
return type(self) == type(other) and self._value == other._value
|
|
|
|
|
|
class EntryVariable(Variable):
|
|
def __init__(self, interpreter, name):
|
|
Variable.__init__(self)
|
|
self.interpreter = interpreter
|
|
self.name = name
|
|
def set(self, value):
|
|
if value is not None:
|
|
self.validate(value)
|
|
self.interpreter.current_entry_vars[self.name] = value
|
|
def value(self):
|
|
return self.interpreter.current_entry_vars.get(self.name, self.default)
|
|
|
|
|
|
class Integer(Variable):
|
|
value_type = int
|
|
default = 0
|
|
|
|
|
|
class EntryInteger(Integer, EntryVariable):
|
|
pass
|
|
|
|
|
|
class String(Variable):
|
|
value_type = six.string_types
|
|
default = ''
|
|
|
|
|
|
class EntryString(String, EntryVariable):
|
|
pass
|
|
|
|
|
|
class MissingField(str):
|
|
def __new__(cls, name):
|
|
self = str.__new__(cls)
|
|
self.name = name
|
|
return self
|
|
def __nonzero__(self):
|
|
return False
|
|
|
|
|
|
class Field(object):
|
|
def __init__(self, interpreter, name):
|
|
self.interpreter = interpreter
|
|
self.name = name
|
|
|
|
def execute(self, interpreter):
|
|
self.interpreter.push(self.value())
|
|
|
|
def value(self):
|
|
try:
|
|
bib_data = self.interpreter.bib_data
|
|
return self.interpreter.current_entry._find_field(self.name, bib_data)
|
|
except KeyError:
|
|
return MissingField(self.name)
|
|
|
|
|
|
class Crossref(Field):
|
|
def __init__(self, interpreter):
|
|
super(Crossref, self).__init__(interpreter, 'crossref')
|
|
|
|
def value(self):
|
|
try:
|
|
value = self.interpreter.current_entry.fields[self.name]
|
|
crossref_entry = self.interpreter.bib_data.entries[value]
|
|
except KeyError:
|
|
return MissingField(self.name)
|
|
return crossref_entry.key
|
|
|
|
|
|
class Identifier(Variable):
|
|
value_type = six.string_types
|
|
def execute(self, interpreter):
|
|
try:
|
|
f = interpreter.vars[self.value()]
|
|
except KeyError:
|
|
raise BibTeXError('can not execute undefined function %s' % self)
|
|
f.execute(interpreter)
|
|
|
|
|
|
class QuotedVar(Variable):
|
|
value_type = six.string_types
|
|
def execute(self, interpreter):
|
|
try:
|
|
var = interpreter.vars[self.value()]
|
|
except KeyError:
|
|
raise BibTeXError('can not push undefined variable %s' % self.value())
|
|
interpreter.push(var)
|
|
|
|
|
|
class Function(object):
|
|
def __init__(self, body=None):
|
|
if body is None:
|
|
body = []
|
|
self.body = body
|
|
|
|
def __repr__(self):
|
|
return u'{0}({1})'.format(type(self).__name__, repr(self.body))
|
|
|
|
def __eq__(self, other):
|
|
return type(self) == type(other) and self.body == other.body
|
|
|
|
def execute(self, interpreter):
|
|
# print 'executing function', self.body
|
|
for element in self.body:
|
|
element.execute(interpreter)
|
|
|
|
|
|
class FunctionLiteral(Function):
|
|
def execute(self, interpreter):
|
|
interpreter.push(Function(self.body))
|
|
|
|
|
|
class Interpreter(object):
|
|
def __init__(self, bib_format, bib_encoding):
|
|
self.bib_format = bib_format
|
|
self.bib_encoding = bib_encoding
|
|
self.stack = []
|
|
self.vars = CaseInsensitiveDict(builtins)
|
|
self.add_variable('global.max$', Integer(20000)) # constants taken from
|
|
self.add_variable('entry.max$', Integer(250)) # BibTeX 0.99d (TeX Live 2012)
|
|
self.add_variable('sort.key$', EntryString(self, 'sort.key$'))
|
|
self.macros = {}
|
|
self.output_buffer = []
|
|
self.output_lines = []
|
|
self.entry_vars = defaultdict(dict)
|
|
|
|
def push(self, value):
|
|
# print 'push <%s>' % value
|
|
self.stack.append(value)
|
|
# print 'stack:', self.stack
|
|
|
|
def pop(self):
|
|
try:
|
|
value = self.stack.pop()
|
|
except IndexError:
|
|
raise BibTeXError('pop from empty stack')
|
|
# print 'pop <%s>' % value
|
|
return value
|
|
|
|
def get_token(self):
|
|
return next(self.bst_script)
|
|
|
|
def add_variable(self, name, value):
|
|
if name in self.vars:
|
|
raise BibTeXError('variable "{0}" already declared as {1}'.format(name, type(value).__name__))
|
|
self.vars[name] = value
|
|
|
|
def output(self, string):
|
|
self.output_buffer.append(string)
|
|
|
|
def newline(self):
|
|
output = wrap(u''.join(self.output_buffer))
|
|
self.output_lines.append(output)
|
|
self.output_lines.append(u'\n')
|
|
self.output_buffer = []
|
|
|
|
def run(self, bst_script, citations, bib_files, min_crossrefs):
|
|
"""Run bst script and return formatted bibliography."""
|
|
|
|
self.bst_script = iter(bst_script)
|
|
self.citations = citations
|
|
self.bib_files = bib_files
|
|
self.min_crossrefs = min_crossrefs
|
|
|
|
for command in self.bst_script:
|
|
name = command[0]
|
|
args = command[1:]
|
|
method = 'command_' + name.lower()
|
|
if hasattr(self, method):
|
|
getattr(self, method)(*args)
|
|
else:
|
|
print('Unknown command', name)
|
|
|
|
return u''.join(self.output_lines)
|
|
|
|
def command_entry(self, fields, ints, strings):
|
|
for id in fields:
|
|
name = id.value()
|
|
self.add_variable(name, Field(self, name))
|
|
self.add_variable('crossref', Crossref(self))
|
|
for id in ints:
|
|
name = id.value()
|
|
self.add_variable(name, EntryInteger(self, name))
|
|
for id in strings:
|
|
name = id.value()
|
|
self.add_variable(name, EntryString(self, name))
|
|
|
|
def command_execute(self, command_):
|
|
# print 'EXECUTE'
|
|
command_[0].execute(self)
|
|
|
|
def command_function(self, name_, body):
|
|
name = name_[0].value()
|
|
self.add_variable(name, Function(body))
|
|
|
|
def command_integers(self, identifiers):
|
|
# print 'INTEGERS'
|
|
for identifier in identifiers:
|
|
self.vars[identifier.value()] = Integer()
|
|
|
|
def command_iterate(self, function_group):
|
|
function = function_group[0].value()
|
|
self._iterate(function, self.citations)
|
|
|
|
def _iterate(self, function, citations):
|
|
f = self.vars[function]
|
|
for key in citations:
|
|
self.current_entry_key = key
|
|
self.current_entry = self.bib_data.entries[key]
|
|
self.current_entry_vars = self.entry_vars[key]
|
|
f.execute(self)
|
|
self.currentEntry = None
|
|
|
|
def command_macro(self, name_, value_):
|
|
name = name_[0].value()
|
|
value = value_[0].value()
|
|
self.macros[name] = value
|
|
|
|
def command_read(self):
|
|
# print 'READ'
|
|
p = self.bib_format(
|
|
encoding=self.bib_encoding,
|
|
macros=self.macros,
|
|
person_fields=[],
|
|
wanted_entries=self.citations,
|
|
)
|
|
self.bib_data = p.parse_files(self.bib_files)
|
|
self.citations = self.bib_data.add_extra_citations(self.citations, self.min_crossrefs)
|
|
self.citations = list(self.remove_missing_citations(self.citations))
|
|
# for k, v in self.bib_data.items():
|
|
# print k
|
|
# for field, value in v.fields.items():
|
|
# print '\t', field, value
|
|
# pass
|
|
|
|
def remove_missing_citations(self, citations):
|
|
for citation in citations:
|
|
if citation in self.bib_data.entries:
|
|
yield citation
|
|
else:
|
|
print_warning('missing database entry for "{0}"'.format(citation))
|
|
|
|
def command_reverse(self, function_group):
|
|
function = function_group[0].value()
|
|
self._iterate(function, reversed(self.citations))
|
|
|
|
def command_sort(self):
|
|
def key(citation):
|
|
return self.entry_vars[citation]['sort.key$']
|
|
self.citations.sort(key=key)
|
|
|
|
def command_strings(self, identifiers):
|
|
#print 'STRINGS'
|
|
for identifier in identifiers:
|
|
self.vars[identifier.value()] = String()
|
|
|
|
@staticmethod
|
|
def is_missing_field(field):
|
|
return isinstance(field, MissingField)
|
|
|