# vim: fileencoding=utf-8 # 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 unicode_literals import codecs from pybtex.bibtex.exceptions import BibTeXError from pybtex.bibtex.utils import scan_bibtex_string from pybtex.database.output import BaseWriter class Writer(BaseWriter): """Outputs BibTeX markup""" unicode_io = True def quote(self, s): r""" >>> w = Writer() >>> print(w.quote('The World')) "The World" >>> print(w.quote(r'The \emph{World}')) "The \emph{World}" >>> print(w.quote(r'The "World"')) {The "World"} >>> try: ... print(w.quote(r'The {World')) ... except BibTeXError as error: ... print(error) String has unmatched braces: The {World """ self.check_braces(s) if '"' not in s: return '"%s"' % s else: return '{%s}' % s def check_braces(self, s): """ Raise an exception if the given string has unmatched braces. >>> w = Writer() >>> w.check_braces('Cat eats carrots.') >>> w.check_braces('Cat eats {carrots}.') >>> w.check_braces('Cat eats {carrots{}}.') >>> w.check_braces('') >>> w.check_braces('end}') >>> try: ... w.check_braces('{') ... except BibTeXError as error: ... print(error) String has unmatched braces: { >>> w.check_braces('{test}}') >>> try: ... w.check_braces('{{test}') ... except BibTeXError as error: ... print(error) String has unmatched braces: {{test} """ tokens = list(scan_bibtex_string(s)) if tokens: end_brace_level = tokens[-1][1] if end_brace_level != 0: raise BibTeXError('String has unmatched braces: %s' % s) def _encode(self, text): r"""Encode text as LaTeX. >>> w = Writer(encoding='ASCII') >>> print(w._encode(u'1970–1971.')) 1970--1971. >>> w = Writer(encoding='UTF-8') >>> print(w._encode(u'1970–1971.')) 1970–1971. >>> w = Writer(encoding='UTF-8') >>> print(w._encode(u'100% noir')) 100\% noir """ import latexcodec # NOQA return codecs.encode(text, 'ulatex+{}'.format(self.encoding)) def _encode_with_comments(self, text): r"""Encode text as LaTeX, preserve comments. >>> w = Writer(encoding='ASCII') >>> print(w._encode_with_comments(u'1970–1971. %% † RIP †')) 1970--1971. %% \dag\ RIP \dag >>> w = Writer(encoding='UTF-8') >>> print(w._encode_with_comments(u'1970–1971. %% † RIP †')) 1970–1971. %% † RIP † """ return u'%'.join(self._encode(part) for part in text.split(u'%')) def _write_field(self, stream, type, value): stream.write(u',\n %s = %s' % (type, self.quote(self._encode(value)))) def _format_name(self, stream, person): def join(l): return ' '.join([name for name in l if name]) first = person.get_part_as_text('first') middle = person.get_part_as_text('middle') prelast = person.get_part_as_text('prelast') last = person.get_part_as_text('last') lineage = person.get_part_as_text('lineage') s = '' if last: s += join([prelast, last]) if lineage: s += ', %s' % lineage if first or middle: s += ', ' s += join([first, middle]) return s def _write_persons(self, stream, persons, role): # persons = getattr(entry, role + 's') if persons: names = u' and '.join(self._format_name(stream, person) for person in persons) self._write_field(stream, role, names) def _write_preamble(self, stream, preamble): if preamble: stream.write(u'@preamble{%s}\n\n' % self.quote(self._encode_with_comments(preamble))) def write_stream(self, bib_data, stream): self._write_preamble(stream, bib_data.preamble) first = True for key, entry in bib_data.entries.items(): if not first: stream.write(u'\n') first = False stream.write(u'@%s' % entry.original_type) stream.write(u'{%s' % key) # for role in ('author', 'editor'): for role, persons in entry.persons.items(): self._write_persons(stream, persons, role) for type, value in entry.fields.items(): self._write_field(stream, type, value) stream.write(u'\n}\n')