summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/ejpecp/getmref.py
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/ejpecp/getmref.py')
-rw-r--r--macros/latex/contrib/ejpecp/getmref.py1809
1 files changed, 1809 insertions, 0 deletions
diff --git a/macros/latex/contrib/ejpecp/getmref.py b/macros/latex/contrib/ejpecp/getmref.py
new file mode 100644
index 0000000000..3241aac3aa
--- /dev/null
+++ b/macros/latex/contrib/ejpecp/getmref.py
@@ -0,0 +1,1809 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+##################################################################################
+#
+# getmref.py - gets the references links to MathSciNet through the BatchMRef:
+# https://mathscinet.ams.org/batchmref?qdata=xmldocument
+#
+# Copyright (C) 2017 Sigitas Tolusis, VTeX Ltd., Jim Pitman, Dept. Statistics,
+# U.C. Berkeley and Lolita Tolene, VTeX Ltd.
+# E-mail: latex-support@vtex.lt
+# http://www.stat.berkeley.edu/users/pitman
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# Requires:
+# - python ver. >=2.2
+# - [ for option --enc=auto ]
+# Universal Encoding Detector library, written by Mark Pilgrim.
+#
+# Usage:
+# getmref.py <bbl or tex file>
+#
+# Program (description):
+# - makes inputfile copy to <inputfilename>.getmref.bak;
+# - for each successful bibitem reference search adds line \MR{<mrid>},
+# where <mrid> is data from XML tag <mrid> without front symbols "MR";
+# - writes all adds to <inputfilename>;
+# - generates log file <inputfilename>.getmref.log;
+# - writes to stdout log info
+#
+# Changes:
+# 2004/04/26 - \bibitem line removed from the query
+# 2017/01/12 - input file may contain 'amsrefs', 'bibtex' and 'tex' type
+# references (all at once);
+# input references can be formatted as 'amsrefs', 'bibtex',
+# 'tex' or 'html' type references
+#
+#
+##################################################################################
+
+__version__ = "GetMRef, v2.4"
+
+import sys
+import os
+import re
+import string
+import urllib
+import urllib2
+import ssl
+import shutil
+import logging
+from time import time, sleep
+from xml.dom.minidom import parseString
+from xml.parsers.expat import ExpatError
+
+BASICFORMATTER = logging.Formatter('%(message)s')
+DEBUGFORMATTER = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+
+log = logging.getLogger(__name__)
+log.addHandler(logging.NullHandler())
+flog = slog = log
+
+
+class RefTypes(object):
+ """ This class declares recognized bibliography reference formats
+
+ Formats description
+ -------------------
+ Source: only AMS
+ "tex": LaTeX code without any specific beginning/ending;
+ MR number is given in plain text
+ "html": <a href="http://www.ams.org/mathscinet-getitem?mr=7digits">
+ 7digits
+ </a>
+
+ Source: only user
+ "bibitem": \bibitem[<name-year info>]{<cite_key>}
+ ...
+ \MR{<7 digits>}
+ \endbibitem,
+ where '[<name-year info>]' and '\endbibitem' are optional
+ Requires environment
+ \begin{thebibliography}{<ref no>}
+ ...
+ \end{thebibliography}
+
+ Source: AMS and user
+ "bibtex": @<ref type>{<cite_key>,
+ <key1>={<value1>},
+ <key2>={<value2>},
+ MRNUMBER={<7 digits>}
+ ...}
+ "amsrefs": \bib{<cite_key>}{<ref type>}{
+ <key1>={<value1>},
+ <key2>={<value2>},
+ review={\MR{<7 digits>}}
+ ...}
+ Requires environment
+ \begin{biblist}
+ ...
+ \end{biblist}
+ """
+
+ TEX = "tex"
+ BIBITEM = "bibitem"
+ IMS = "ims"
+ BIBTEX = "bibtex"
+ AMSREFS = "amsrefs"
+ HTML = "html"
+
+ # Reference input formats
+ ITYPES = (BIBITEM, BIBTEX, AMSREFS)
+
+ # Reference output formats
+ OTYPES = (TEX, BIBTEX, IMS, AMSREFS, HTML)
+
+
+class LessThanFilter(logging.Filter):
+ """ This class allows to add an upper bound to the logged messages
+
+ Example
+ -------
+ One needs to log all non-error messages to stdout, and all errors
+ (higher level) only to stderr
+ """
+
+ def __init__(self, exclusive_maximum, name=""):
+ super(LessThanFilter, self).__init__(name)
+ self.max_level = exclusive_maximum
+
+ def filter(self, record):
+ # A non-zero return means we log this message
+ return 1 if record.levelno <= self.max_level else 0
+
+
+class FilesHandler(RefTypes):
+ """ This class unites methods and attributes related to
+ files I/O actions """
+
+ IN = 'in'
+ BAK = 'bak'
+ OUT = 'out'
+ DATA = 'data'
+ TMP = 'tmp'
+ AUX = 'aux'
+ BIB = 'bib'
+ HTML = 'html'
+ LOG = 'log'
+ ERR = 'err'
+
+ # File status map:
+ # if True file will be open until closed;
+ # if False it will be opened on demand
+ FILE_STATUS = {OUT: True,
+ LOG: False,
+ ERR: False,
+ DATA: True,
+ BIB: True,
+ AUX: True,
+ HTML: True,
+ TMP: False}
+
+ READ = 'r'
+ WRITE = 'w'
+
+ GMR_SUFFIX = 'getmref'
+
+ def __init__(self, infile, outputtype):
+ """ Initiate file handling methods and attributes
+
+ Parameters
+ ----------
+ infile : str or None
+ Path to input file
+ outputtype : str or None
+ Required bibliography reference output format type
+ """
+
+ self.infile = infile
+ self._basename = os.path.splitext(infile)[0]
+
+ # Determining needed file types for given reference output type
+ msg = ("The given references will be formatted in '%s' format. "
+ % (outputtype if outputtype is not None else "orig"))
+
+ unnecessary = [self.DATA, self.BIB, self.AUX, self.HTML]
+ if outputtype in [self.BIBTEX, self.IMS]:
+ unnecessary = [self.DATA, self.HTML]
+ # Referring to 'BIB' file as 'DATA'
+ self.DATA = self.BIB
+ msg += "Additional files will be created: *.%s, *.%s" \
+ % (self.BIB, self.AUX)
+ elif outputtype == self.HTML:
+ unnecessary = [self.DATA, self.BIB, self.AUX]
+ # Referring to 'HTML' file as 'DATA'
+ self.DATA = self.HTML
+ msg += "Additional file will be created: *.%s" % self.HTML
+ elif outputtype in [self.TEX, self.AMSREFS]:
+ unnecessary = [self.HTML, self.BIB, self.AUX]
+ msg += "Additional file will be created: *.%s" % self.DATA
+
+ self.files = dict()
+ for suffix, status in self.FILE_STATUS.items():
+ # Deleting old files
+ self._delete(suffix)
+ if suffix in unnecessary:
+ continue
+ if status:
+ self.open(suffix)
+ continue
+ self.files.update({suffix: self.get_fname(suffix)})
+
+ flog.info("File: %s" % infile)
+ if not (os.path.isfile(infile) and os.path.exists(infile)):
+ logging.shutdown()
+ for suffix in self.FILE_STATUS:
+ self.close_and_delete(suffix)
+ raise ValueError("Provided source file does not exist! "
+ "Please provide the valid one.")
+
+ flog.debug("Workdir: %s" % os.path.abspath(os.path.dirname(infile)))
+ flog.debug(msg)
+
+ def set_fname(self, suffix):
+ """ Set a filepath for a file with the provided suffix
+
+ Parameters
+ ----------
+ suffix : str
+ File suffix without punctuation
+
+ Returns
+ -------
+ str
+ """
+ return ("%s.%s.%s" % (self._basename, self.GMR_SUFFIX, suffix)
+ if suffix != self.IN else self.infile)
+
+ def get_fname(self, suffix):
+ """ Get filepath of a file with the required suffix
+
+ Parameters
+ ----------
+ suffix : str
+ File suffix without punctuation
+
+ Returns
+ -------
+ str
+ If requested file is open, returning file object name,
+ or the filepath otherwise
+ """
+ target = self.files.get(suffix, self.set_fname(suffix))
+ if isinstance(target, file):
+ return target.name
+ return target
+
+ def open(self, suffix, mask=WRITE):
+ """ Open file for the selected action
+
+ Parameters
+ ----------
+ suffix : str
+ File suffix without punctuation
+ mask : str
+ Possible actions are read or write
+
+ File is opened and file object is added to the dictionary
+ for later access
+ """
+ self.files.update({suffix: file(self.get_fname(suffix), mask)})
+
+ def read(self, suffix):
+ """ Get the content of a file with the required suffix
+
+ Parameters
+ ----------
+ suffix : str
+ File suffix without punctuation
+
+ Yields
+ ------
+ str
+ """
+ with open(self.get_fname(suffix), self.READ) as ifile:
+ for iline in ifile:
+ yield iline
+
+ def write(self, suffix, msg):
+ """ Write to the file with the required suffix
+ only if this file is open
+
+ Parameters
+ ----------
+ suffix : str
+ File suffix without punctuation
+ msg : str
+ """
+ target = self.files.get(suffix, None)
+ if isinstance(target, file):
+ target.write(msg)
+
+ def close(self, suffix):
+ """ Close the file with the required suffix
+
+ Parameters
+ ----------
+ suffix : str
+ File suffix without punctuation
+ """
+ fileobj = self.files.get(suffix, "")
+ if isinstance(fileobj, file):
+ fileobj.close()
+
+ def _delete(self, suffix):
+ """ Delete the file with the required suffix
+
+ Parameters
+ ----------
+ suffix : str
+ File suffix without punctuation
+ """
+ dfile = self.get_fname(suffix)
+ try:
+ os.unlink(dfile)
+ flog.debug("Deleted: %s" % os.path.split(dfile)[1])
+ except OSError:
+ if os.path.isfile(dfile) and os.path.exists(dfile):
+ flog.exception("Can't remove file: %s" % dfile)
+
+ def close_and_delete(self, suffix):
+ """ Close and delete the file with the required suffix
+
+ Parameters
+ ----------
+ suffix : str
+ File suffix without punctuation
+ """
+ self.close(suffix)
+ self._delete(suffix)
+
+ def close_files(self):
+ """ Close all open files and logging instances,
+ create backup of the input file and
+ overwrite it with the new content, delete auxiliary files
+ """
+ flog.debug("Closing files...")
+ for suffix in self.files:
+ self.close(suffix)
+
+ self._delete(self.TMP)
+
+ bfile = self.get_fname(self.BAK)
+ if os.path.exists(bfile):
+ shutil.copy2(self.infile, bfile)
+ else:
+ os.rename(self.infile, bfile)
+ flog.debug("Created backup of the input file: %s"
+ % os.path.split(bfile)[1])
+
+ ofile = self.get_fname(self.OUT)
+ if os.path.exists(ofile):
+ shutil.copy2(ofile, self.infile)
+ self._delete(self.OUT)
+ else:
+ os.rename(ofile, self.infile)
+ flog.debug("The input file is overwritten with: %s"
+ % os.path.split(ofile)[1])
+
+ logging.shutdown()
+
+
+class RefHandler(RefTypes):
+ """ This class unites methods and attributes related to bibliography
+ reference format types and their content modifications """
+
+ # Bibliography environment
+ BIBL_ENV = "environment"
+ BIBL_BEGIN = "begin"
+ BIBL_END = "end"
+
+ # Declaration of typical reference type ending and
+ # MR id format for this type
+ FORMAT_PROPERTIES = {
+ RefTypes.BIBTEX: {
+ "ref_ending": "}",
+ "mr_format": ",\nMRNUMBER={%s},\n"
+ },
+ RefTypes.AMSREFS: {
+ "ref_ending": "}",
+ "mr_format": ",\nreview={\MR{%s}},\n"
+ },
+ RefTypes.BIBITEM: {
+ "ref_ending": "\\endbibitem",
+ "mr_format": "\n\\MR{%s}\n"
+ }
+ }
+
+ # Meaningful reference keys for AMS Batch MR Lookup query
+ KEYS = {"0AUTH": ("author",),
+ "1TTL": ("title", "maintitle"),
+ "2JOUR": ("journal", "journaltitle", "fjournal", "booktitle"),
+ "3VID": ("volume",),
+ "4IID": ("number", "series"),
+ "5PID": ("pages",),
+ "6YNO": ("year", "date"),
+ "7ISSN": ("issn", "isrn", "isbn")}
+
+ PATTERN_KEY_VALUE = "^\s*([\w-]+)\s*=\s*(.*?)$"
+
+ PATTERN_LINE_END = r'(\r?\n)+'
+ PATTERN_PAR = r'(\r?\n){2}'
+
+ PATTERN_BIBL_ENV = (r'\s*\\(?P<envstatus>begin|end)\s*'
+ r'\{(thebibliography|biblist\*?)\}(.*)$')
+
+ PATTERN_BIBRE = r'^\s*\\bibitem.*'
+ PATTERN_BIBREF = (r'\s*\\bibitem\s*(?P<biblabel>\[.*?\])*?\s?'
+ r'\{(?P<citekey>.*?)\}(?P<text>.*)$')
+ PATTERN_BIBTEX = (r'^\s*(@\S+)(?<!@preamble)\s*'
+ r'{(?P<citekey>\S+)\s*,(?P<text>.*)$')
+ PATTERN_AMSREFS = r"\\bib\s*{(?P<citekey>.*)}\s*{(.*)}\s*{(?P<text>.*)$"
+
+ def __init__(self, outputtype):
+ """ Initiate reference handling methods and attributes
+
+ Parameters
+ ----------
+ outputtype : str or None
+ Required reference output format type
+ """
+
+ self.outputtype = outputtype
+
+ self.re_bibl_env = re.compile(self.PATTERN_BIBL_ENV)
+ self.re_bibre = re.compile(self.PATTERN_BIBRE)
+ self.re_bibreF = re.compile(self.PATTERN_BIBREF, re.S)
+ self.re_bibtex = re.compile(self.PATTERN_BIBTEX, re.M)
+ self.re_amsrefs = re.compile(self.PATTERN_AMSREFS, re.M)
+
+ self.re_lineend = re.compile(self.PATTERN_LINE_END)
+ self.re_par = re.compile(self.PATTERN_PAR)
+ self.re_key_value = re.compile(self.PATTERN_KEY_VALUE, re.DOTALL)
+
+ def find_reference(self, line):
+ """ Identify reference environment or element by using regex patterns
+
+ Parameters
+ ----------
+ line : str
+
+ Returns
+ -------
+ str or None
+ If match is found, returns the reference type, None otherwise
+ dict
+ Dictionary contains regex pattern group names and their matches
+
+ The value of the key 'text' is the line part without user
+ defined strings, such as citekey and biblabel, because they may
+ contain some misleading information for BatchMRef query
+ """
+
+ elems = {self.BIBL_ENV: self.re_bibl_env,
+ self.BIBTEX: self.re_bibtex,
+ self.AMSREFS: self.re_amsrefs}
+
+ # BIBITEM search starts with an additional check
+ # which other reference types doesn't have
+ if self.re_bibre.search(line) is not None:
+ elems = {self.BIBITEM: self.re_bibreF}
+
+ for reftype, pattern in elems.items():
+ match = pattern.search(line)
+ if match is not None:
+ return reftype, match.groupdict()
+ elif reftype == self.BIBITEM:
+ # If final search for BIBITEM fails, it means that the typical
+ # structure for this reference type is placed on several lines,
+ # therefore the current line is prepended to the next input line
+ return reftype, {"line": line}
+ return None, dict()
+
+ def extract_keys_data(self, lines):
+ """ Extract values from selected keys in reference
+
+ Parameters
+ ----------
+ lines : list
+
+ Returns
+ -------
+ str
+ Output contains extracted values separated by commas
+ """
+ flog.debug(">> Extracting key values from reference")
+ querystring = ""
+ user_key = None
+ found = list()
+ for line in lines:
+ match = self.re_key_value.search(line)
+ if match:
+ user_key, user_value = match.groups()
+ user_key = user_key.lower()
+ for key, value in sorted(self.KEYS.items()):
+ if user_key in value and (user_key in found or key not in found):
+ found.append(key)
+ found.append(user_key)
+ querystring += "%s, " % user_value.strip().rstrip(",")\
+ .strip().strip('"')\
+ .strip().rstrip("}")\
+ .lstrip("{").strip()
+ break
+ elif len(found) > 0 and found[-1] == user_key:
+ querystring = "%s %s, " % (querystring.strip(", "),
+ line.strip().rstrip(",").strip().strip('"')
+ .strip().rstrip("}").lstrip("{")
+ .strip().rstrip(",").strip())
+
+ return querystring.strip(", ")
+
+ def insert_mrid(self, reftype, refstring, mrid):
+ """ Format MR number according to the input reference format and
+ append it to the input reference
+
+ Parameters
+ ----------
+ reftype : str
+ Determined input bibliography reference item type
+ refstring : str
+ Input bibliography reference item content
+ mrid : str
+ MR number returned by query to BatchMRef
+
+ Returns
+ -------
+ str
+ Output contains input bibliography reference element including
+ according to reftype formatted mrid.
+ """
+ properties = self.FORMAT_PROPERTIES.get(reftype, None)
+ if properties is None:
+ outstring = self.re_lineend.sub('\n', refstring)
+ return '%s\\MR{%s}\n\n' % (outstring, mrid)
+
+ mr_string = properties["mr_format"] % mrid
+ ending_index = refstring.rfind(properties["ref_ending"])
+ if ending_index == -1:
+ paragraph = self.re_par.search(refstring)
+ if paragraph is not None:
+ ending_index = paragraph.start()
+ mr_string += "\n"
+
+ if ending_index != -1:
+ return "%s%s%s" % (refstring[:ending_index].strip().strip(","),
+ mr_string,
+ refstring[ending_index:].lstrip())
+
+ return refstring.strip() + mr_string + "\n"
+
+ def insert_citekey(self, outref, citekey, biblabel, querystring):
+ """ Add a cite key, extracted from an input reference item,
+ to the reference content, returned by the query to BatchMRef
+ (XML tag <outref>), in the required reference output format
+
+ Parameters
+ ----------
+ outref : str or None
+ Reference item content returned by the query to BatchMRef
+ citekey : str
+ Input bibliography reference item cite key
+ biblabel : str or None
+ Input bibliography reference item label,
+ provided in optional parameter of reference type of BIBITEM
+ querystring : str
+ Input bibliography reference item formatted for query
+ to BatchMRef
+
+ Returns
+ -------
+ str or None
+ Returned string is the outref including the citekey and
+ the biblabel (if provided) if reference has been found in
+ the AMS MR DB, else string is formatted according to the
+ requested output type.
+
+ If allowed output type is not provided, None is returned
+ """
+
+ if self.outputtype is None:
+ return None
+
+ if outref is None:
+ if self.outputtype == self.TEX:
+ return ("\\bibitem%s{%s}\n Not Found!\n\n"
+ % (biblabel if biblabel is not None else "",
+ citekey))
+ if self.outputtype == self.BIBTEX:
+ return '@MISC {%s,\n NOTE = {Not Found!}\n}\n\n' % citekey
+ if self.outputtype == self.IMS:
+ return ('@MISC {%s,\n HOWPUBLISHED = {%s},\n}\n\n'
+ % (citekey, querystring))
+ if self.outputtype == self.AMSREFS:
+ return ('\\bib{%s}{misc}{\n note = {Not Found!}\n}\n\n'
+ % citekey)
+ if self.outputtype == self.HTML:
+ return '<!-- %s -->\nNot Found!\n<br/><br/>\n\n' % citekey
+ return None
+
+ outref = outref.strip() + '\n\n'
+ if self.outputtype == self.TEX:
+ return ('\\bibitem%s{%s}\n%s'
+ % (biblabel if biblabel is not None else "",
+ citekey, outref))
+ if self.outputtype in [self.BIBTEX, self.IMS]:
+ return self.re_bibtex.sub(r'\1 {%s,' % citekey, outref)
+ if self.outputtype == self.AMSREFS:
+ return self.re_amsrefs.sub(r'\\bib\0{%s}{\2}' % citekey, outref)
+ if self.outputtype == self.HTML:
+ return '<!-- %s -->\n%s<br/><br/>\n' % (citekey, outref)
+ return None
+
+
+class RefElement(object):
+ """ This is a container for one bibliography reference item,
+ containing all data related to it """
+
+ FORMAT_PROPERTIES = RefHandler.FORMAT_PROPERTIES
+
+ def __init__(self, refid=None, reftype=None, citekey=None, biblabel=None):
+ """ Initiate reference item container
+
+ Parameters
+ ----------
+ refid : int or None
+ reftype : str or None
+ Input bibliography reference type (one of RefTypes.ITYPES).
+ citekey : str or None
+ Input bibliography reference cite key
+ biblabel : str or None
+ Input bibliography reference label,
+ provided in the optional parameter of RefTypes.BIBITEM type
+ reference item
+ """
+
+ self.reftype = reftype
+ self.refid = refid
+ self.citekey = citekey
+ self.biblabel = biblabel
+
+ self.orig_lines = list()
+ self.cleaned_lines = list()
+ self.query_lines = list()
+ self.comment_lines = list()
+
+ self.errno = 0
+ self._init_querystring = None
+ self._querystring = None
+ self._mrid = None
+ self.outref = None
+
+ def normalize(self, lines):
+ """ Normalize the reference item content
+ Parameters
+ ----------
+ lines : list
+
+ Returns
+ -------
+ str
+ Returned string doesn't contain trailing spaces and
+ typical ending for the reference of reftype (if found)
+ """
+ nstring = re.sub('\s+', ' ', ''.join(lines)).strip()
+ ending = self.FORMAT_PROPERTIES.get(self.reftype,
+ dict()).get("ref_ending", "")
+ ending_index = nstring.rfind(ending)
+ if ending_index != -1:
+ nstring = nstring[:ending_index].strip()
+ return nstring
+
+ @property
+ def init_querystring(self):
+ if self._init_querystring is not None:
+ return self._init_querystring
+
+ flog.debug(">> Normalizing the reference")
+ self._init_querystring = self.normalize(self.query_lines)
+
+ return self._init_querystring
+
+ @property
+ def querystring(self):
+ if self._querystring is not None:
+ return self._querystring
+ return self.init_querystring
+
+ @querystring.setter
+ def querystring(self, istring):
+ self._querystring = istring
+
+ @property
+ def mrid(self):
+ return self._mrid
+
+ @mrid.setter
+ def mrid(self, mrid):
+ """ Normalize MR number, returned by the query to BatchMRef
+
+ Parameters
+ ----------
+ mrid : str
+
+ Returns
+ -------
+ str
+ If original MR number is shorter than 7 symbols, prepending 0,
+ till it reaches 7 symbol length
+ """
+ if mrid is not None:
+ self._mrid = mrid.encode('ascii').lstrip("MR").rjust(7, '0')
+
+ def __repr__(self):
+ result = "<%s:\n" % self.__class__.__name__
+ for key, value in sorted(self.__dict__.items()):
+ if key.startswith("_"):
+ continue
+ result += " %s = %s\n" % (key, repr(value))
+ result += " >\n"
+ return result
+
+ def __str__(self):
+ return self.__repr__()
+
+
+class RefsContainer(object):
+ """ This is a container holding as many bibliography reference items as
+ is allowed by the denoted query to BatchMRef limit and data common
+ to all of them """
+
+ def __init__(self):
+ super(RefsContainer, self).__init__()
+ self.elems = list()
+ self.qerrno = 0
+
+ def append_elem(self, ref_element):
+ """ Add bibliography reference item instance to the container
+
+ Parameters
+ ----------
+ ref_element : RefElement() instance
+ """
+
+ self.elems += (ref_element,)
+
+ def get_elem_by_refid(self, refid):
+ """ Get bibliography reference item instance by its id
+
+ Parameters
+ ----------
+ refid : int
+
+ Returns
+ -------
+ RefElement() instance or None
+ If element with required id is not found, None is returned
+ """
+
+ elem = [e for e in self.elems if e.refid == refid]
+ if elem:
+ return elem[0]
+
+ def __str__(self):
+ result = "<%s:\n" % self.__class__.__name__
+ for key, value in sorted(self.__dict__.items()):
+ if key == "elems":
+ for elem in value:
+ result += " %s" % repr(elem)
+ elif key not in ["elems", "qresult", "xml"]:
+ result += " GLOBAL: {} = {}\n".format(key, value)
+ result += " >\n"
+ return result
+
+
+class QueryHandler(RefTypes):
+ """ This class unites methods and attributes related to actions necessary
+ for the AMS BatchMRef query """
+
+ AUTO_ENC = "auto"
+ LATIN1 = 'latin1'
+ ASCII = "ascii"
+
+ AMS_URL = 'https://mathscinet.ams.org/batchmref'
+
+ # AMS BatchMRef limit of items no per query
+ QUERY_ITEMS_LIMIT = 100
+
+ QUERY_XML_HEADING_STRING = '<?xml version="1.0" encoding="UTF-8"?>\n'
+
+ QUERY_HEADING_STRING = (
+ '<mref_batch>\n'
+ ' %s'
+ '</mref_batch>'
+ )
+
+ QUERY_ITEM_STRING = (
+ '<mref_item outtype="%s">\n'
+ ' <inref>\n'
+ ' %s\n'
+ ' </inref>\n'
+ ' <myid>%d</myid>\n'
+ '</mref_item>\n'
+ )
+
+ QUERY_FORMATS = {
+ RefTypes.TEX: RefTypes.TEX,
+ RefTypes.BIBTEX: RefTypes.BIBTEX,
+ RefTypes.IMS: RefTypes.BIBTEX,
+ RefTypes.AMSREFS: RefTypes.AMSREFS,
+ RefTypes.HTML: RefTypes.HTML,
+ None: RefTypes.TEX
+ }
+
+ PATTERN_MREF_ITEM = '(\<mref_item outtype="(?:bibtex|tex|amsrefs|html)"\>.*?\</mref_item\>)'
+ PATTERN_BATCH_ERROR = '\<batch_error\>(.*?)\</batch_error\>'
+
+ # AMS gives the following message in HTML if requested website is broken
+ AMS_MSG = "The AMS Website is temporarily unavailable."
+
+ def __init__(self, encoding, outputtype, refscontainer, address=AMS_URL):
+ """ Initiate query to BatchMRef handling methods and attributes
+
+ Parameters
+ ----------
+ encoding : str
+ Input file encoding
+ outputtype : str or None
+ Reference output format type passed for BatchMRef query
+ refscontainer : RefsContainer() instance
+ address : str
+ BatchMRef query address
+ """
+
+ self.encoding = encoding
+ flog.debug("Provided encoding format: %s" % encoding)
+ self.address = address
+ self.query_format = self.QUERY_FORMATS.get(outputtype, self.TEX)
+ flog.debug("Query settings: URL = %s, output format = %s"
+ % (address, self.query_format))
+ self.outputtype = outputtype
+
+ self.errno = 0
+ self.qresult = None
+ self.qcode = None
+ self.xml = None
+ self.re_mref_item = re.compile(self.PATTERN_MREF_ITEM, re.DOTALL)
+ self.re_batch_error = re.compile(self.PATTERN_BATCH_ERROR, re.DOTALL)
+
+ self._refscontainer = refscontainer
+ self.query_elems = list()
+
+ @property
+ def refscontainer(self):
+ return self._refscontainer
+
+ def _encode_str(self, istring):
+ """ Change query string encoding into the ASCII
+
+ Parameters
+ ----------
+ istring : str
+
+ Returns
+ -------
+ str
+ """
+
+ str_enc = self.encoding
+ self.errno = 0
+ if str_enc == self.AUTO_ENC:
+ detector = UniversalDetector()
+ detector.feed(istring)
+ detector.close()
+ str_enc = detector.result.get('encoding', self.ASCII)
+ flog.debug(">> Determined string encoding: %s" % str_enc)
+
+ if str_enc == self.ASCII:
+ return istring
+ if str_enc is None:
+ flog.debug(">> Encoding determination has FAILED! ")
+ return istring
+
+ try:
+ return istring.decode(str_enc.lower()).encode(self.ASCII,
+ errors='replace')
+ except:
+ flog.debug(">> encoding given reference element FAILED!")
+ msg = (">> encoding given reference element FAILED!\n"
+ "[Input string]:\n%s\n" % istring)
+ flog.exception(msg)
+ self.errno = -2
+ return istring
+
+ @staticmethod
+ def _escape_tex(istring):
+ """ Convert TeX symbols into XML valid symbols
+
+ Parameters
+ ----------
+ istring : str
+
+ Returns
+ -------
+ str
+ """
+
+ flog.debug(">> Converting TeX symbols into XML valid symbols")
+ return reduce(lambda a, b: string.replace(a, b[0], b[1]),
+ (istring, ("\\&", '&amp;'), ("<", '&lt;'), (">", '&gt;'),
+ ("&", '&amp;'), (r"\ndash ", "-")))
+
+ def _parse_str(self, istring, check=False):
+ """ Parse string into XML object
+
+ Parameters
+ ----------
+ istring : str
+ check : bool
+ If True, checking if string parses to valid XML.
+ If False, saving parsed XML or an error code
+ if parsing was unsuccessful
+ """
+
+ try:
+ xml = parseString(istring)
+ if not check:
+ self.xml = xml
+ else:
+ flog.debug("VALIDATING XML string ...")
+ flog.debug(">> XML contains no errors")
+ except ExpatError as err:
+ flog.debug(">> Parsing given XML FAILED!")
+ msg = (">> Parsing given XML FAILED!\n",
+ "[Parse query]:\n%s\n" % istring)
+ flog.exception(msg)
+ self.errno = err.code
+
+ def prepare_query_str(self, refid, querystring):
+ """ Format the reference as an XML string and validate it
+
+ Parameters
+ ----------
+ refid : int
+ RefElement() instance id
+ querystring : str
+
+ Returns
+ -------
+ int
+ If query string was encoded and parsed into valid XML
+ successfully, it is appended to a future query strings list
+ and error code is set to 0
+
+ If something went wrong, non-zero value is returned
+ """
+
+ self.errno = 0
+ flog.debug("PREPARING query reference")
+
+ single_qstring = self._encode_str(
+ self.QUERY_ITEM_STRING % (self.query_format,
+ self._escape_tex(querystring),
+ refid)
+ )
+ flog.debug(">> Formed query XML:\n"
+ + "~" * 70 + "\n%s\n" % single_qstring + "~" * 70)
+
+ # Checking if formed string is a valid XML
+ self._parse_str(single_qstring, check=True)
+ if self.errno != 0:
+ return self.errno
+
+ self.query_elems.append(single_qstring)
+ return self.errno
+
+ def _send_query(self, querystring):
+ """ Send query to BatchMRef
+
+ Parameters
+ ----------
+ querystring : str
+ Validated XML query string, containing as many reference items
+ as QueryHandler.QUERY_ITEMS_LIMIT allows
+
+ If request to BatchMRef was successful, saving query result,
+ otherwise non-zero error code is saved
+ """
+
+ queryinfo = {'qdata': querystring}
+ queryval = urllib.urlencode(queryinfo)
+ try:
+ flog.debug("SENDING query ...")
+ req = urllib2.Request(url=self.address, data=queryval)
+ flog.debug(">> Query POST data: %s" % req.get_data())
+ context = ssl._create_unverified_context()
+ batchmref = urllib2.urlopen(req, context=context)
+ self.qcode = batchmref.getcode()
+ flog.debug(">> Query result code: %s" % self.qcode)
+ self.qresult = batchmref.read()
+
+ if self.qcode == 200 and \
+ self.qresult.startswith(self.QUERY_XML_HEADING_STRING):
+ flog.debug(">> Query result string:\n"
+ + "~"*70 + "\n%s\n" % self.qresult.strip() + "~"*70)
+ else:
+ msg = "\n%s" % self.AMS_MSG if self.AMS_MSG in self.qresult else ""
+ flog.debug(">> Query FAILED! %s" % msg)
+ flog.error("Query returned an error:\n%s\n\n%s"
+ % (msg, self.qresult))
+ self.errno = self.qcode if self.qcode != 200 else -2
+ self.qresult = None
+
+ batchmref.close()
+ except:
+ msg = ">> Query FAILED!"
+ flog.debug(msg)
+ flog.exception(msg)
+ self.errno = -2
+ self.qresult = None
+
+ @staticmethod
+ def _extract_xml_data(xml_elem, tag):
+ """ Extract text data from an XML object
+
+ Parameters
+ ----------
+ xml_elem : XML object
+ tag : str
+ XML tag of interest
+
+ Returns
+ -------
+ str or None
+ Content of XML element with the requested tag.
+ If element with the tag hasn't been found, None is returned
+ """
+
+ childelem = xml_elem.getElementsByTagName(tag)
+ if childelem:
+ childnodes = childelem[0].childNodes
+ if childnodes:
+ return childnodes[0].data
+
+ def _analyze_xml(self, xml):
+ """ Extract reference data from the BatchMRef returned XML string,
+ parsed into XML object
+
+ Parameters
+ ----------
+ xml : XML object
+
+ If no matches have been found in the AMS MR DB,
+ current RefElement() instance gets a non-zero error code.
+ Otherwise MR number and reference content (if requested output type
+ is not None) are saved in the current RefElement() instance
+ """
+
+ mref_item = xml.getElementsByTagName("mref_item")[0]
+ refid = int(self._extract_xml_data(mref_item, "myid"))
+ elem = self.refscontainer.get_elem_by_refid(refid)
+
+ matches = self._extract_xml_data(mref_item, "matches")
+ if matches == '1':
+ flog.debug(">> MRef DB: reference `%s' found!" % elem.citekey)
+ elem.mrid = self._extract_xml_data(mref_item, "mrid")
+ flog.debug(">> MRef ID: %s" % elem.mrid)
+
+ if self.outputtype is not None:
+ elem.outref = self._extract_xml_data(mref_item, "outref")
+ flog.debug(">> MRef output reference:\n"
+ + "~"*70 + "\n%s\n" % elem.outref.strip() + "~"*70)
+ else:
+ elem.errno = -1
+ flog.debug(">> MRef DB: reference `%s' not found!" % elem.citekey)
+
+ def query(self):
+ """ Send a request to AMS BatchMRef and analyze the returned data
+
+ If query result contains 'batch_error' element or returned
+ XML string can't be parsed into XML object,
+ RefsContainer() instance gets a non-zero error code.
+ """
+
+ self.errno = 0
+ self.qresult = None
+
+ querystring = (self.QUERY_XML_HEADING_STRING
+ + self.QUERY_HEADING_STRING % ("\n".join(self.query_elems)))
+ if self.errno == 0:
+ self._send_query(querystring)
+ if self.qresult is not None:
+ error_obj = self.re_batch_error.search(self.qresult)
+ if error_obj:
+ flog.debug(">> Query XML contains an ERROR!")
+ flog.error("[batch_error]:\n%s\n\n[querystring]:\n%s"
+ % (self._encode_str(error_obj.group(1)),
+ querystring))
+ self.errno = -2
+ flog.debug("Splitting query result and analyzing parts separately")
+ for item_qresult in self.re_mref_item.finditer(self.qresult):
+ self.xml = None
+ self._parse_str(self._encode_str(item_qresult.group()))
+ if self.xml is not None:
+ self._analyze_xml(self.xml)
+
+ self.refscontainer.qerrno = self.errno
+ self.query_elems = list()
+
+
+class HandleBBL(RefTypes):
+ """ This is the main class containing and initiating other classes'
+ methods and attributes for provided input data processing """
+
+ # MR number pattern matching all recognized reference formats
+ PATTERN_MR = r'MRNUMBER=\{.*?\}(,|)|review=\{\\MR\{.*?\}\}(,|)|\\MR\{.*?\}'
+
+ PATTERN_BIBRE_LINE = r'^%.*\r?\n$'
+ PATTERN_BIBRE_PART = r'\s*(.*?)(?<!\\)%.*\r?\n$'
+
+ PATTERN_TEX_ACCENTS = r"""(?:\{|)\\(?:"|'|`|\^|-|H|~|c|k|=|b|\.|d|r|u|v|A)(?:|\{)([a-zA-Z])\}(?:\}|)"""
+ PATTERN_BRACED_LETTERS = r"""(\s)(?<!\\)([a-zA-Z]*)\{([A-Z]+)\}"""
+
+ # Mark of the input file ending
+ EOF = "EOF"
+
+ # Default bibstyle format
+ PLAIN = 'plain'
+
+ def __init__(self, inputfile, encoding, clean_comments,
+ itemno, wait, outputtype, bibstyle, debug, version=str()):
+ """ Initiate all methods and attributes required to process input data
+
+ Parameters
+ ----------
+ inputfile : str or None
+ encoding : str
+ Input file encoding
+ clean_comments : bool
+ If TeX comments cleaning is selected,
+ full comment lines will be moved to the beginning of each
+ identified bibliography reference item
+ itemno : int
+ Limit of reference items per query to BatchMRef
+ wait: int
+ Pause length after each query to BatchMRef
+ outputtype : str or None
+ If not None, additional files with the requested references,
+ extracted from the AMS MR DB in the requested output format,
+ will be generated
+ bibstyle : str or None
+ Used only if the requested output type is BIBTEX or IMS
+ debug : int
+ If debug value is greater than 0, debug messages will be
+ written to the FileHandler.LOG file. Also, depending on the
+ given debug value, final data written to the input file will
+ contain TeX comments with query data.
+ version : str
+ """
+
+ self.refscontainer = RefsContainer()
+
+ self.fh = FilesHandler(inputfile, outputtype)
+ self.rh = RefHandler(outputtype)
+ self.qh = QueryHandler(encoding, outputtype, self.refscontainer)
+
+ if itemno < self.qh.QUERY_ITEMS_LIMIT:
+ self.qh.QUERY_ITEMS_LIMIT = itemno
+ self.wait = wait
+
+ self.outputtype = outputtype
+ self.bibstyle = bibstyle
+ flog.debug("Comments will be cleaned from the output: %s"
+ % clean_comments)
+ self.clean_comments = clean_comments
+ self.debug = debug
+ self.version = version
+
+ self.re_bibre_line = re.compile(self.PATTERN_BIBRE_LINE)
+ self.re_bibre_part = re.compile(self.PATTERN_BIBRE_PART)
+ self.re_MR = re.compile(self.PATTERN_MR)
+ self.re_tex_accents = re.compile(self.PATTERN_TEX_ACCENTS)
+ self.re_braced_letters = re.compile(self.PATTERN_BRACED_LETTERS)
+
+ self.eof = False
+ self.ifile_end_lines = list()
+
+ @property
+ def icontent(self):
+ """ Input file content """
+ return self.fh.read(self.fh.IN)
+
+ @property
+ def write(self):
+ return self.fh.write
+
+ @property
+ def get_fname(self):
+ return self.fh.get_fname
+
+ def preprocess_ofiles(self):
+ """ Depending on the requested bibliography output type,
+ certain files are pre-filled with required data.
+ Writing action is fulfilled only if requested file was pre-opened.
+ """
+ self.write(self.fh.AUX, '\\bibstyle{%s}\n' % self.bibstyle)
+ self.write(self.fh.HTML, "<!DOCTYPE html>\n<html>\n<body>\n\n")
+
+ def postprocess_ofiles(self, refcount):
+ """ Depending on the requested bibliography output type,
+ certain files are filled up with the required data.
+ Writing action is fulfilled only if requested file was pre-opened.
+
+ Parameters
+ ----------
+ refcount: int
+ If refcount is 0, it means no references have been found
+ in the input file, and pre-opened additional files are deleted
+
+ If requested bibliography output type is TEX,
+ number of bibliography items found is written to the first line of
+ FileHandler.DATA file. Therefore this file is written twice into.
+ """
+
+ if refcount == 0:
+ self.fh.close_and_delete(self.fh.DATA)
+ self.fh.close_and_delete(self.fh.AUX)
+ return None
+
+ datafilepath = self.get_fname(self.fh.DATA)
+ self.write(self.fh.AUX,
+ '\\bibdata{%s}' % os.path.splitext(datafilepath)[0])
+ self.write(self.fh.HTML, "\n</body>\n</html>\n")
+
+ # Formatting the DATA file output according to requested output format
+ obiblenv = {
+ self.TEX: {
+ "begin": "\\begin{thebibliography}{%s}\n"
+ "\\csname bibmessage\\endcsname\n\n",
+ "end": "\\end{thebibliography}\n"
+ },
+ self.AMSREFS: {
+ "begin": "\\begin{bibdiv}\n\\begin{biblist}\n\n",
+ "end": "\\end{biblist}\n\\end{bibdiv}"
+ }
+ }
+
+ strings = obiblenv.get(self.outputtype, None)
+ if strings is None:
+ return None
+
+ start_string, finish_string = sorted(strings.values())
+ self.write(self.fh.DATA, finish_string)
+
+ # Total items count is known only after processing all references and
+ # writing to the DATA file, therefore 'thebibliography' environment
+ # starting string is written to this file when all processing is
+ # finished
+ self.fh.close(self.fh.DATA)
+ os.rename(datafilepath, self.get_fname(self.fh.TMP))
+ self.fh.open(self.fh.TMP, self.fh.READ)
+
+ if self.outputtype == self.TEX:
+ start_string = start_string % refcount
+ self.fh.open(self.fh.DATA, self.fh.WRITE)
+ self.write(self.fh.DATA, start_string)
+
+ shutil.copyfileobj(self.fh.files[self.fh.TMP],
+ self.fh.files[self.fh.DATA])
+
+ def _remove_tex_comments(self, line):
+ """ Remove TeX comments
+
+ Parameters
+ ----------
+ line : str
+
+ Returns
+ -------
+ str
+ """
+ fmtline = self.re_bibre_line.sub('', line)
+ if fmtline:
+ matchobj = self.re_bibre_part.search(fmtline)
+ if matchobj is not None:
+ return "%s\n" % matchobj.groups(1)[0]
+ return fmtline
+ return fmtline
+
+ def _remove_tex_accents(self, line):
+ """ Remove TeX accents and braces around upper case letters
+
+ BatchMRef may not found a reference in the AMS MR DB because of
+ braces and accents present in reference string (tested), therefore
+ accented letters "{\'a}" and "\'{a}" are changed to plain "a".
+ Also "{ABC}" is changed to "ABC".
+
+ Parameters
+ ----------
+ line : str
+
+ Returns
+ -------
+ str
+ """
+ mline = self.re_tex_accents.sub(r'\1', line)
+ if mline:
+ return self.re_braced_letters.sub(r'\1\2\3', mline)
+ return mline
+
+ def gather_records(self, require_env):
+ """ Extract bibliography reference items from the input file
+
+ Parameters
+ ----------
+ require_env : bool
+ If True, get bibliography reference items only inside
+ the bibliography environment. If False, gel all bibliography
+ reference items found in the input file
+
+ Yields
+ -------
+ str
+ Denotes reference format type (one of ITYPES),
+ bibliography environment state (RefHandler.BIBL_BEGIN or
+ RefHandler.BIBL_END),
+ or input file end mark (EOF)
+
+ RefElement() instance, str, or None
+ If reference of one of ITYPES type has been found,
+ a RefElement() instance is returned with the following
+ attributes filled in:
+ reftype, citekey, biblabel,
+ orig_lines, cleaned_lines, query_lines
+
+ If end of input file has been determined, None is returned
+
+ Otherwise current line is returned
+ """
+
+ def sort_comments_out(comment_lines):
+ """ Assign gathered comment lines to the rightful reference item
+
+ Parameters
+ ----------
+ comment_lines : list
+
+ Returns
+ -------
+ list
+ Comment lines, belonging to current reference item
+ list
+ Comment lines, belonging to the next reference item
+ """
+
+ next_elem_comments = []
+ reversed_comments = comment_lines[::-1]
+ reversed_comments_backup = comment_lines[::-1]
+ advanced_by = 0
+ for no, cline in enumerate(reversed_comments):
+ if len(element.orig_lines) < (no + 1 + advanced_by):
+ break
+ while not element.orig_lines[-(no + 1 + advanced_by)].strip():
+ # skipping empty lines
+ advanced_by += 1
+ if cline == element.orig_lines[-(no + 1 + advanced_by)]:
+ reversed_comments_backup.pop(0)
+ next_elem_comments.append(reversed_comments[no])
+ current_elem_comments = reversed_comments_backup[::-1]
+ return current_elem_comments, next_elem_comments
+
+ # Allowing gathering the references according to
+ # the bibliography environment status
+ envmap = {self.rh.BIBL_BEGIN: True,
+ self.rh.BIBL_END: False,
+ "not found": False if require_env else True}
+ gather = envmap["not found"]
+ search = True
+
+ multiline = ""
+ element = RefElement()
+ envstatus = None
+ for line in self.icontent:
+ line = multiline + line
+ clean_line = self._remove_tex_comments(line)
+
+ if not clean_line and element.orig_lines:
+ element.orig_lines.append(line)
+ element.comment_lines.append(line)
+ continue
+
+ reftype = None
+ if search:
+ reftype, additional_info = self.rh.find_reference(clean_line)
+
+ if require_env and reftype == self.rh.BIBL_ENV:
+ if element.reftype is not None:
+ # Full bibliography item
+ element.comment_lines, next_elem_comments = \
+ sort_comments_out(element.comment_lines)
+ yield element.reftype, element
+ element = RefElement()
+ element.comment_lines = next_elem_comments
+
+ # Bibliography environment
+ envstatus = additional_info.pop("envstatus", None)
+ if envstatus in envmap:
+ gather = envmap[envstatus]
+ search = gather
+ yield envstatus, line
+ continue
+
+ elif reftype in self.ITYPES:
+ multiline = additional_info.get("line", "")
+ if multiline:
+ continue
+
+ if element.reftype is not None:
+ # Full bibliography item
+ element.comment_lines, next_elem_comments = \
+ sort_comments_out(element.comment_lines)
+ yield element.reftype, element
+ element = RefElement()
+ element.comment_lines = next_elem_comments
+
+ if gather:
+ element.reftype = reftype
+ element.citekey = additional_info.get("citekey", None)
+ element.biblabel = additional_info.get("biblabel", None)
+ element.orig_lines.append(line)
+
+ mrid_free_line = self.re_MR.sub('', clean_line)
+ element.cleaned_lines.append(mrid_free_line)
+
+ ref_format_free_line = additional_info.get("text", clean_line)
+ mrid_free_line = self.re_MR.sub('', ref_format_free_line)
+ accent_free_line = self._remove_tex_accents(mrid_free_line)
+ element.query_lines.append(accent_free_line)
+ continue
+
+ if gather and element.reftype is not None:
+ element.orig_lines.append(line)
+ mrid_free_line = self.re_MR.sub('', clean_line)
+ element.cleaned_lines.append(mrid_free_line)
+ accent_free_line = self._remove_tex_accents(mrid_free_line)
+ element.query_lines.append(accent_free_line)
+ else:
+ # Before and after the bibliography environment
+ yield envstatus, line
+
+ if element.reftype is not None:
+ # The last full bibliography item
+ element.comment_lines, _ = sort_comments_out(element.comment_lines)
+ yield element.reftype, element
+
+ yield self.EOF, None
+
+ def transfer_to_file(self):
+ """ After each query to BatchMRef write gathered data into files
+
+ Returns
+ -------
+ int
+ Number of references, for which data has been successfully
+ obtained
+ """
+
+ successful = 0
+
+ for elem in self.refscontainer.elems:
+ if self.refscontainer.qerrno != 0:
+ elem.errno = self.refscontainer.qerrno
+ outstring = ''.join(elem.cleaned_lines if self.clean_comments else
+ elem.orig_lines)
+
+ elem.outref = self.rh.insert_citekey(
+ elem.outref, elem.citekey, elem.biblabel,
+ elem.normalize(elem.cleaned_lines[1:]))
+ if elem.mrid is not None:
+ outstring = self.rh.insert_mrid(elem.reftype, outstring, elem.mrid)
+ slog.info(elem.mrid)
+ elif elem.errno == -1:
+ slog.warn('NotFound')
+ else:
+ slog.error('QueryError')
+
+ if self.clean_comments:
+ outstring = "".join(elem.comment_lines) + outstring
+
+ if self.debug == 1:
+ outstring = '%%%% %s\n%s' % (elem.querystring, outstring)
+ elif self.debug == 2:
+ outstring = '%%%% %s\n%s' % (elem.errno, outstring)
+ elif self.debug == 3:
+ outstring = '%%%% %s\n%%%% %s\n%s' % (elem.querystring,
+ elem.errno,
+ outstring)
+
+ flog.debug("\n" + ">" * 70
+ + "\nFINAL reference with MR id in original format:\n"
+ + "\n%s\n" % outstring.strip())
+
+ if elem.outref is not None:
+ flog.debug("FINAL reference in '%s' format:\n" % self.outputtype
+ + "\n%s\n" % elem.outref.strip() + "<" * 70)
+ self.write(self.fh.OUT, outstring)
+ self.write(self.fh.DATA, elem.outref if elem.outref else "")
+ self.write(self.fh.AUX, '\\citation{%s}\n' % elem.citekey)
+
+ if elem.errno == 0 and self.refscontainer.qerrno == 0:
+ successful += 1
+
+ if self.eof:
+ while self.ifile_end_lines:
+ self.write(self.fh.OUT, self.ifile_end_lines.pop(0))
+
+ self.refscontainer = RefsContainer()
+ self.qh._refscontainer = self.refscontainer
+
+ return successful
+
+ def get_mr_codes(self, require_env):
+ """ Analyze input file content and process found reference items
+
+ Parameters
+ ----------
+ require_env : bool
+ If True, and if no bibliography reference items have been found
+ inside the bibliography environment, or an environment hasn't
+ been found at all, parameter is set to False this method and
+ reruns itself in order to search reference items in
+ the whole input file.
+
+ Returns
+ -------
+ int
+ Total bibliography reference items found
+ int
+ Total number of references, for which data has been
+ successfully obtained
+ int
+ Reference items processed with errors count
+
+ If reference item of ITYPES has been found, current
+ RefElement() instance attribute 'refid' is assigned a value
+ """
+
+ msg = ("in the bibliography environment only"
+ if require_env else "in the whole input file")
+ flog.debug("SEARCHING for reference items: %s" % msg)
+
+ total = 0
+ valid = 0
+ successful = 0
+ records = self.gather_records(require_env=require_env)
+ pseudo_citekey = 0
+ for reftype, record in records:
+ if reftype == self.EOF:
+ self.eof = True
+
+ elif reftype not in self.ITYPES:
+ if reftype != self.rh.BIBL_END:
+ self.write(self.fh.OUT, record)
+ else:
+ self.ifile_end_lines.append(record)
+ continue
+
+ elif valid == 0 or valid % self.qh.QUERY_ITEMS_LIMIT != 0:
+ total += 1
+
+ record.refid = total
+ if not record.citekey:
+ pseudo_citekey += 1
+ record.citekey = '%s' % pseudo_citekey
+
+ flog.debug("=" * 70)
+ flog.debug("FOUND reference %s: type=%s, cite_key=%s, biblabel=%s"
+ % (total, reftype, record.citekey, record.biblabel))
+
+ if reftype != self.BIBITEM:
+ record.querystring = self.rh.extract_keys_data(record.query_lines)
+ self.refscontainer.append_elem(record)
+ record.errno = self.qh.prepare_query_str(record.refid,
+ record.querystring)
+ if record.errno == 0:
+ valid += 1
+
+ if valid != 0 and (valid % self.qh.QUERY_ITEMS_LIMIT == 0
+ or self.eof):
+ self.qh.query()
+ successful += self.transfer_to_file()
+ valid = 0
+ if not self.eof:
+ sleep(self.wait)
+
+ if total == 0 and require_env:
+ # If no bibliography items were found in the bibliography
+ # environment, then trying to search for them everywhere
+ # in the input file
+ flog.debug("FOUND no references! Changing the search mode ... ")
+ self.eof = False
+ self.ifile_end_lines = list()
+ self.fh.close(self.fh.OUT)
+ self.fh.open(self.fh.OUT)
+ return self.get_mr_codes(require_env=False)
+
+ if self.ifile_end_lines:
+ self.transfer_to_file()
+
+ flog.debug("=" * 70)
+ errors = total - successful
+ return total, successful, errors
+
+ def run(self, require_env):
+ """ Main method
+
+ Parameters
+ ----------
+ require_env : bool
+
+ Returns
+ -------
+ get_mr_codes() output
+ """
+
+ slog.info("# %s #\nJob started:" % self.version)
+ starttime = time()
+
+ self.preprocess_ofiles()
+ total, successful, errors = self.get_mr_codes(require_env=require_env)
+ self.postprocess_ofiles(refcount=total)
+
+ flog.info(" total: %s, found: %s, not found: %s, time: %ss"
+ % (total, successful, errors, int(round(time()-starttime))))
+
+ slog.info("Job ended")
+ slog.info("Total: %s, found: %s, not found: %s"
+ % (total, successful, errors))
+ slog.info('Job completed in %ss' % int(round(time()-starttime)))
+
+ self.fh.close_files()
+ return total, successful, errors
+
+
+if __name__ == '__main__':
+ import argparse
+
+ # Logging to console
+ osh = logging.StreamHandler(stream=sys.stdout)
+ osh.setFormatter(BASICFORMATTER)
+ osh.setLevel(logging.INFO)
+ osh.addFilter(LessThanFilter(logging.INFO))
+
+ esh = logging.StreamHandler(stream=sys.stderr)
+ esh.setFormatter(BASICFORMATTER)
+ esh.setLevel(logging.WARN)
+
+ slog = logging.getLogger("%s.StreamLogger" % __name__)
+ slog.setLevel(logging.INFO)
+ slog.addHandler(osh)
+ slog.addHandler(esh)
+
+ # Logging to files
+ flog = logging.getLogger("%s.FileLogger" % __name__)
+ flog.setLevel(logging.DEBUG)
+
+ def setup_logging_files(debug, basename=""):
+ """ Set up logging files
+
+ Parameters
+ ----------
+ debug : int
+ basename: str
+ Input file name
+
+ Returns
+ -------
+ logging instance
+ """
+
+ if debug == 0:
+ log_min_level = logging.INFO
+ log_max_level = logging.INFO
+ formatter = BASICFORMATTER
+ else:
+ log_min_level = logging.DEBUG
+ log_max_level = logging.WARN
+ formatter = DEBUGFORMATTER
+
+ ofh = logging.FileHandler(filename="{}.{}.{}".format(basename,
+ FilesHandler.GMR_SUFFIX,
+ FilesHandler.LOG),
+ mode='w', delay=True)
+ ofh.setFormatter(formatter)
+ ofh.setLevel(log_min_level)
+ ofh.addFilter(LessThanFilter(log_max_level))
+ flog.addHandler(ofh)
+
+ efh = logging.FileHandler(filename="{}.{}.{}".format(basename,
+ FilesHandler.GMR_SUFFIX,
+ FilesHandler.ERR),
+ mode='w', delay=True)
+ efh.setFormatter(DEBUGFORMATTER)
+ efh.setLevel(logging.ERROR)
+ flog.addHandler(efh)
+ return flog
+
+ VERSION = __version__.split("-")[0]
+ DESCRIPTION = (
+ "Tool %s, is designed for: " % VERSION
+ + "(1) getting MR numbers for given references from AMS MRef database, "
+ + "(2) formatting the given references in one of AMS allowed formats. "
+ + "Maintainer: L.Tolene <lolita.tolene@vtex.lt>."
+ )
+
+ def get_cmd_args():
+ """ Command line input parser """
+
+ parser = argparse.ArgumentParser(
+ description=DESCRIPTION,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter
+ )
+ parser.add_argument("filepath", help="References containing file")
+ parser.add_argument(
+ "--enc", '-e', type=str, default=QueryHandler.LATIN1,
+ help="Source file encoding or 'auto'"
+ )
+ parser.add_argument(
+ "--format", '-f', choices=set(RefTypes.OTYPES),
+ help="Outputs the given references in provided format. "
+ "For more information about these formats please "
+ "consult the AMS MRef tool website. The 'ims' format "
+ "is almost the same as the 'bibtex' format"
+ )
+ parser.add_argument(
+ "--bibstyle", '-s', default=HandleBBL.PLAIN,
+ help="BibTeX style. For more information please consult "
+ "the BibTeX documentation"
+ )
+ parser.add_argument(
+ "--nobibenv", action='store_true',
+ help="If activated, references are searched throughout "
+ "all source file content; otherwise searching only "
+ "inside the bibliography environment. Currently "
+ "recognizable are the 'thebibliography' and 'biblist' "
+ "environments"
+ )
+ parser.add_argument(
+ "--clean", '-c', action='store_true',
+ help="If activated, cleans comments appearing in references"
+ )
+ parser.add_argument(
+ "--itemno", default=100, type=int,
+ help="Maximum item count for one AMS query. "
+ "AMS batchmref has a limit of 100 items per query."
+ )
+ parser.add_argument(
+ "--wait", default=10, type=int,
+ help="time (in seconds) to wait between queries to AMS batchmref."
+ )
+ parser.add_argument(
+ "--debug", '-d', choices={0, 1, 2, 3}, default=0, type=int,
+ help="Outputs additional info for debugging purposes."
+ )
+ parser.add_argument(
+ "--version", '-v', action='version', version=VERSION,
+ help="Module version."
+ )
+ args = parser.parse_args()
+ return (args.filepath, args.enc, args.format, args.bibstyle,
+ args.nobibenv, args.clean, args.itemno, args.wait, args.debug)
+
+ # Get input parameter values
+ inputfile, encoding, output_format, bibstyle, nobibenv, clean, itemno, wait, debug \
+ = get_cmd_args()
+
+ # Load additional library is needed
+ if encoding == QueryHandler.AUTO_ENC:
+ from chardet.universaldetector import UniversalDetector
+
+ # Setup logging files
+ flog = setup_logging_files(debug=debug,
+ basename=os.path.splitext(inputfile)[0])
+
+ # Create HandleBBL() instance
+ bblobj = HandleBBL(inputfile=inputfile, encoding=encoding,
+ clean_comments=clean, itemno=itemno, wait=wait,
+ outputtype=output_format, bibstyle=bibstyle,
+ debug=debug, version=VERSION)
+
+ # Process input file
+ bblobj.run(require_env=not nobibenv)