Пример #1
0
    def fetch_and_update(self, bibtex=True, latex_format='EU', append_bbl=False):
        existing_keys = self.bib().entries.keys() if bibtex else self.bbl_keys()
        existing_keys = [k.lower() for k in existing_keys]

        replace_keys = ODict()
        new_entries = ODict()
        type_name = 'BibTeX' if bibtex else 'LaTeX({})'.format(latex_format)

        for ref in self.references.values():
            if ref.key.lower() in existing_keys:
                if DEBUG:
                    print('skip existing: {}'.format(ref.key))
                continue
            if Key.is_unknown(ref.key):
                print('WARNING: skip non-existing but unknown-type key {}'.format(ref.key))
                continue

            try:
                print('fetching', type_name, 'from inspire:', ref.key, end=' ')
                if bibtex:
                    ref.fetch_bibtex()
                else:
                    ref.fetch_latex(latex_format)
                sys.stdout.flush()
                time.sleep(0.3)
            except RecordNotFound or MultipleRecordsFound as e:
                print('\nERROR: {}'.format(e))
                continue

            if ref.new_key:
                replace_keys[ref.old_key] = ref
                print('->', ref.new_key, end=' ')
                sys.stdout.flush()
                time.sleep(0.3)
            if ref.key.lower() not in existing_keys:
                existing_keys.append(ref.key.lower())
                new_entries[ref.key] = ref
                print('[new entry]', end='')
            print('')

        replacements = list()
        for ref in replace_keys.values():
            for appearance in ref.positions:
                replacements.append((appearance, ref.old_key, ref.new_key))
        self.replace_text(replacements)
        self.write_tex()

        new_ref_contents = '\n'.join(r.content for r in new_entries.values())
        if bibtex:
            self.append_and_update_bib(new_ref_contents)
        else:
            self.modify_and_write_bbl(new_ref_contents, append_bbl)
Пример #2
0
class TeX(object):
    CITE_REGEX = re.compile(r'(?P<pre>(\\cite(\[.*?\])?{))(?P<body>.*?)}', re.DOTALL)
    CITE_BIB_IN_TEX = re.compile(r'\\bibliography{(.*?)}', re.DOTALL)
    CITE_BIB_IN_BBL = re.compile(r'\\bibitem{(.*?)}', re.DOTALL)
    COMMENTS_REGEX = re.compile(r'((?:^|[^\\])(?:\\\\)*)%.*$', re.MULTILINE)

    @classmethod
    def strip_comment(cls, string):
        return cls.COMMENTS_REGEX.sub(r'\1', string)

    def __init__(self, filename):
        self.text = None
        errors = []
        possible_paths = [filename, filename + '.tex']
        for path in possible_paths:
            try:
                with open(path, mode='r') as file:
                    self.text_original = file.read()
                    self.text = self.text_original
                    self.filename = path
                    self.stem = os.path.splitext(path)[0]
                    break
            except IOError as e:
                errors.append(e)
                pass
        if self.text is None:
            raise FileLookupFailedError(errors=errors, paths=possible_paths)

        self._bib_name = None
        self._bib = None
        self._bbl = None

        # generate references
        text_uncommented = self.strip_comment(self.text)
        self.references = ODict()  # Order is important!
        for cite in self.CITE_REGEX.finditer(text_uncommented):
            pos = Position(str=text_uncommented[:cite.start()])
            pos.shift(cite.group('pre'))
            for key_raw in re.split(r',', cite.group('body')):
                stripping = re.match(r'^(\s*)(\S+)(\s*)$', key_raw)
                pos.shift(stripping.group(1))
                key = stripping.group(2)
                if key not in self.references:
                    self.references[key] = Ref(key, position=pos.copy())
                else:
                    self.references[key].positions.append(pos.copy())
                pos.shift(stripping.group(2)).shift(stripping.group(3))
                pos.shift(',')

    def write_tex(self):
        with open(self.filename, mode='w') as file:
            file.write(self.text)

    def bbl_name(self):
        return self.stem + '.bbl'

    def bbl(self):
        if self._bbl is None:
            if self.bbl_name() and os.path.exists(self.bbl_name()):
                try:
                    with open(self.bbl_name(), mode='r') as file:
                        self._bbl = file.read()
                except:
                    pass
            self._bbl = self._bbl or ""
        return self._bbl

    def modify_and_write_bbl(self, new_content, append=True):
        begin = '\\begin{thebibliography}{99}'
        end = '\\end{thebibliography}'
        if append:
            sep = re.split(r'\\end\s*{\s*thebibliography\s*}', self.bbl(), maxsplit=1)
            (bbl, footer) = sep if len(sep) == 2 else (sep[0], "")
            self._bbl = '\n'.join([bbl, new_content, end + footer])
        else:
            self._bbl = '\n\n'.join([begin, new_content, end])

        with open(self.bbl_name(), mode='w') as file:
            file.write(self.bbl())

    def bib_name(self):
        if self._bib_name is None:
            bib_keys = self.CITE_BIB_IN_TEX.findall(self.strip_comment(self.text))
            stem = None
            for bib_key in bib_keys:
                for bib in re.split(r'\s*,\s*', bib_key):
                    if stem is None:
                        stem = bib
                    elif stem != bib:
                        raise MultipleBibError
            if stem is None:
                self._bib_name = False  # for "not found"
            else:
                self._bib_name = os.path.join(os.path.dirname(self.filename), stem + '.bib')
        return self._bib_name

    def bib(self):
        if self._bib is None:
            if self.bib_name() and os.path.exists(self.bib_name()):
                try:
                    self._bib = pybtex.database.parse_file(self.bib_name(), bib_format='bibtex')
                except:
                    pass
            self._bib = self._bib or pybtex.database.BibliographyData()
        return self._bib

    # pybtex output are a bit buggy and avoided.
    # def append_bib(self, entries):
    #     # self._bib.add_entries(entries)  # maybe buggy?
    #     for k in entries.order:
    #         self._bib.add_entry(k, entries[k])
    # def update_bib(self):
    #     if self.bib_name():
    #         self._bib.to_file(self.bib_name(), bib_format='bibtex')
    #     else:
    #         raise RuntimeError()

    def append_and_update_bib(self, new_text):
        with open(self.bib_name(), mode='a') as file:
            file.write('\n' + new_text + '\n')
        self._bib = None  # clear and to be reload

    def bbl_keys(self):
        try:
            with open(self.bbl_name(), mode='r') as file:
                return self.CITE_BIB_IN_BBL.findall(self.strip_comment(file.read()))
        except IOError:
            return []

    def fetch_and_update(self, bibtex=True, latex_format='EU', append_bbl=False):
        existing_keys = self.bib().entries.keys() if bibtex else self.bbl_keys()
        existing_keys = [k.lower() for k in existing_keys]

        replace_keys = ODict()
        new_entries = ODict()
        type_name = 'BibTeX' if bibtex else 'LaTeX({})'.format(latex_format)

        for ref in self.references.values():
            if ref.key.lower() in existing_keys:
                if DEBUG:
                    print('skip existing: {}'.format(ref.key))
                continue
            if Key.is_unknown(ref.key):
                print('WARNING: skip non-existing but unknown-type key {}'.format(ref.key))
                continue

            try:
                print('fetching', type_name, 'from inspire:', ref.key, end=' ')
                if bibtex:
                    ref.fetch_bibtex()
                else:
                    ref.fetch_latex(latex_format)
                sys.stdout.flush()
                time.sleep(0.3)
            except RecordNotFound or MultipleRecordsFound as e:
                print('\nERROR: {}'.format(e))
                continue

            if ref.new_key:
                replace_keys[ref.old_key] = ref
                print('->', ref.new_key, end=' ')
                sys.stdout.flush()
                time.sleep(0.3)
            if ref.key.lower() not in existing_keys:
                existing_keys.append(ref.key.lower())
                new_entries[ref.key] = ref
                print('[new entry]', end='')
            print('')

        replacements = list()
        for ref in replace_keys.values():
            for appearance in ref.positions:
                replacements.append((appearance, ref.old_key, ref.new_key))
        self.replace_text(replacements)
        self.write_tex()

        new_ref_contents = '\n'.join(r.content for r in new_entries.values())
        if bibtex:
            self.append_and_update_bib(new_ref_contents)
        else:
            self.modify_and_write_bbl(new_ref_contents, append_bbl)

    def replace_text(self, replacement_rules):
        # replace from the end to the beginning not to break the positions
        replacement_rules = sorted(replacement_rules, key=lambda x: x[0], reverse=True)
        lines = Position.LINESEP_REGEX.split(self.text)
        for pos, old, new in replacement_rules:
            if lines[pos.l][pos.c:pos.c + len(old)] == old:
                lines[pos.l] = lines[pos.l][:pos.c] + new + lines[pos.l][pos.c + len(old):]
            else:
                raise ReplacementError(pos.l, pos.c, old, new, lines[pos.l][pos.c:pos.c + len(old)])
        self.text = '\n'.join(lines)