import re
import string
import sys
import collections
from pathlib import Path
#        ################################ Variables  #####################
bibtexfields = ['abstract', 'address', 'affiliation', 'annote', 'author', 'booktitle', 'chapter',
                'crossref', 'day', 'doi', 'edition', 'editor', 'file', 'howpublished',
                'institution', 'isbn', 'issn', 'journal', 'keywords', 'month', 'note',
                'number', 'organization', 'pages', 'publisher', 'school', 'series', 'title',
                'url', 'volume', 'year']

# Fields that are output literally in bibtex form
textualfields = ['_code', 'abstract', 'address', 'annote', 'booktitle', 'chapter', 'crossref', 'doi', 'edition',
                 'file', 'howpublished', 'institution', 'isbn', 'issn', 'journal', 'keywords', 'month',
                 'note', 'number', 'organization', 'publisher', 'school', 'series', 'title', 'url', 'volume', 'year']

# Some fields that MAY appear + "_code" and "_type"
otherfields = ['_code', '_type', 'code', 'date-added', 'date-modified',
               'daynote', 'firstpage', 'journal_abbrev', 'lastpage']

# List of all possible fields.
allfields = sorted(bibtexfields + otherfields)

# Fields that MUST be present
minimalfields = ('_type', 'author')

namefields = ['author', 'editor']  # Fields that must be treated as names

# Fields that should not be wrapped (must go complete in the same line)
nowrapfields = ['url', 'doi', 'isbn', 'issn', 'crossref']

# list of all reference types. CURRENTLY NOT USED
alltypes = ('article', 'book', 'booklet', 'conference', 'inbook', 'incollection', 'inproceedings', 'manual',
            'mastersthesis', 'misc', 'phdthesis', 'proceedings', 'techreport', 'unpublished')


strings_month = [
    ('jan', 'January'),
    ('feb', 'February'),
    ('mar', 'March'),
    ('apr', 'April'),
    ('may', 'May'),
    ('jun', 'June'),
    ('jul', 'July'),
    ('aug', 'August'),
    ('sep', 'September'),
    ('oct', 'October'),
    ('nov', 'November'),
    ('dec', 'December'),
]

standard_abbrev = strings_month[:]

xml_tags = [  # xml/html entities
    ('&', '&amp;'),
    ('<', '&lt;'),
    ('>', '&gt;'),
]
# Accent tags are not used. They are handled via latex codec
accent_tags = [  # Accents and other latin symbols
    (r'\"a', "&#228;"),
    (r"\`a", "&#224;"),
    (r"\'a", "&#225;"),
    (r"\~a", "&#227;"),
    (r"\^a", "&#226;"),
    (r'\"e', "&#235;"),
    (r"\`e", "&#232;"),
    (r"\'e", "&#233;"),
    (r"\^e", "&#234;"),
    (r'\"\i', "&#239;"),
    (r"\`\i", "&#236;"),
    (r"\'\i", "&#237;"),
    (r"\^\i", "&#238;"),
    (r'\"i', "&#239;"),
    (r"\`i", "&#236;"),
    (r"\'i", "&#237;"),
    (r"\^i", "&#238;"),
    (r'\"o', "&#246;"),
    (r"\`o", "&#242;"),
    (r"\'o", "&#243;"),
    (r"\~o", "&#245;"),
    (r"\^o", "&#244;"),
    (r'\"u', "&#252;"),
    (r"\`", "&#249;"),
    (r"\'", "&#250;"),
    (r"\^", "&#251;"),
    (r'\"A', "&#196;"),
    (r"\`A", "&#192;"),
    (r"\'A", "&#193;"),
    (r"\~A", "&#195;"),
    (r"\^A", "&#194;"),
    (r'\"E', "&#203;"),
    (r"\`E", "&#200;"),
    (r"\'E", "&#201;"),
    (r"\^E", "&#202;"),
    (r'\"I', "&#207;"),
    (r"\`I", "&#204;"),
    (r"\'I", "&#205;"),
    (r"\^I", "&#206;"),
    (r'\"O', "&#214;"),
    (r"\`O", "&#210;"),
    (r"\'O", "&#211;"),
    (r"\~O", "&#213;"),
    (r"\^O", "&#212;"),
    (r'\"U', "&#220;"),
    (r"\`", "&#217;"),
    (r"\'", "&#218;"),
    (r"\^", "&#219;"),
    (r"\~n", "&#241;"),
    (r"\~N", "&#209;"),
    (r"\c c", "&#231;"), (r"\c C", "&#199;"), (r"\circ", "o")
]

other_tags = [
    ('([^\\\\])~', r'\g<1> '),  # Remove tilde (used in LaTeX for spaces)
    # Remove other unknown commands
    (r'\\textbf{([^{]+)}', r'<b>\g<1></b>'), (r'\\\\emph{([^{]+)}',
                                              r'<em>\g<1></em>'), (r'\\textit{([^{]+)}', r'<i>\1</i>'), (r'\[^ {]+{(.+)}', r'\g<1> ')
]

# Some journal data.
# DOI is useful when a journal uses a common (unique) root
# ISSN is unique but ads does not provides it. We list (when available) two issn: [print, online]
# Regular expression will give us a general (but not exact) matching strategy
journal_data = [
    {
        'regexp': r'Nuc\w*[\.]?\s*Inst\w*[\.]?\s*(and)?\s*Meth\w*[\.]?\s*(in)?\s*(Phys(ics)?[\.]?)?\s*(R(esearch)?[\.]?)?\s*(S(ection)?[\.]?)?\s*B|nimb',
        'nombre': r'Nuclear Instruments and Methods in Physics Research B',
        'abbrev': 'NIMB',
        'doi': '10.1016/j.nimb',
        'issn': ['0168-583X']},
    {
        'regexp': r'Int(ernational)?[\.]?\s*J(our(nal)?)?[\.]?\s*(of)?\s*Q(uantum)?[\.]?\s*Ch(em(istry)?)?[\.]?',
        'nombre': 'International Journal of Quantum Chemistry',
        'abbrev': 'IJQC',
        'doi': '10.1002/qua',
        'issn': [
            '1097-461X',
            '0020-7608']},
    {
        'regexp': r'J(ournal)?[\.]?\s*(of)?\s*Phys(ics)?[\.]?\s*A(\s*:\s*Math(ematical)?\s*(and)?\s*(General)?)?|jpa',
        'nombre': r'Journal of Physics A: Mathematical and General',
        'abbrev': 'JPA',
        'doi': '10.1088/0305-4470',
        'issn': [
            '1751-8113',
            '1751-8121']},
    {'regexp': r'J(ournal)?[\.]?\s*(of)?\s*Phys(ics)?[\.]?\s*B(\s*:\s*At(omic)?[\.]?\s*Mol(ecular)?[\.]?\s*(and)?\s*(Opt(ical)?)?\s*Phys(ics)?)?|jpb',
        'nombre': r'Journal of Physics B: Atomic Molecular and Optical Physics', 'abbrev': 'JPB', 'doi': '10.1088/0953-4075', 'issn': ['0953-4075', '1361-6455']},
    {'regexp': r'Ph\w*[\.]? Rev\w* A|pra', 'nombre': r'Physical Review A',
        'abbrev': 'PRA', 'doi': '10.1103/PhysRevA', 'issn': ['1050-2947', '1094-1622']},
    {'regexp': r'Ph\w*[\.]? Rew* B|prb', 'nombre': r'Physical Review B',
        'abbrev': 'PRB', 'doi': '10.1103/PhysRevB', 'issn': ['1098-0121', '1550-235x']},
    {'regexp': r'Ph\w*[\.]? Rew* C|prc', 'nombre': r'Physical Review C',
        'abbrev': 'PRC', 'doi': '10.1103/PhysRevC', 'issn': ['0556-2813', '1089-490X']},
    {'regexp': r'Ph\w*[\.]? Rev\w*[\.]? L\w*[\.]?|prl', 'nombre': r'Physical Review Letters',
        'abbrev': 'PRL', 'doi': '10.1103/PhysRevLett', 'issn': ['0031-9007', '1079-7114']},
    {'regexp': r'Rev\w*[\.]?\s*(of)?\s*Mod(ern)?\s*Phys(ics)?|rmp',
        'nombre': r'Review of Modern Physics',
        'abbrev': 'RMP',
        'doi': 'Not Available',
        'issn': ['0034-6861',
                 '1539-0756']},
    #
    {'regexp': r'J\w*[\.]? Phy\w*[\.]? Conf\w*[\.]?|jcps',
        'nombre': r"Journal of Physics Conference Series",
        'abbrev': 'JPCS',
        'doi': '10.1088/1742-6596',
        'issn': ['1742-6588 ',
                 '1742-6596']},
    {'regexp': r'Comp\w*[\.]? Phys\w*[\.]? C\w*[\.]?|cpc',
        'nombre': r"Computer Physics Communications",
        'abbrev': 'CPC',
        'doi': '10.1016/j.cpc',
        'issn': ['0010-4655']},
    {'regexp': r'Phys\w*[\.]? Scrip\w*[\.]?',
        'nombre': r"Physica Scripta Volume T",
        'abbrev': 'PS',
        'doi': '10.1238/Physica.Topical',
        'issn': []},
    {'regexp': r'Rad\w*[\.]? Phy\w*[\.]? Chem\w*[\.]?|rpc',
        'nombre': r"Radiation Physics and Chemistry",
        'abbrev': 'RPC',
        'doi': '10.1016/j.radphyschem',
        'issn': []},
    {'regexp': r'F\w*[\.]? Bo\w*[\.]? Sys\w*[\.]?|fbs',
        'nombre': r"Few-Body Systems",
        'abbrev': 'FBS',
        'doi': '10.1007/s00601',
        'issn': []},
    {'regexp': r'Eur\w*[\.]? J\w*[\.]? Phys\w*[\.]?|ejp',
        'nombre': r"European Journal of Physics",
        'abbrev': 'EJP',
        'doi': 'Not Available',
        'issn': ['0143-0807',
                 '1361-6404']},
    {'regexp': r'Phys\w*[\.]? Let\w*[\.]? A|pla', 'nombre': r"Physics Letters A",
        'abbrev': 'PLA', 'doi': 'Not Available', 'issn': ['0375-9601']},
    #
    {'regexp': r'Am\w*[\.]? J\w*[\.]? P|ajp',
        'nombre': r"American Journal of Physics",
        'abbrev': 'AJP',
        'doi': '10.1119/',
        'issn': ['0002-9505']},
    {'regexp': r'J\w*[\.]? Chem\w*[\.]? Phys\w*[\.]?|jcp|JCP',
        'nombre': r"Journal of Chemical Physics",
        'abbrev': 'JPC',
        'doi': 'Not Available',
        'issn': ['0021-9606',
                 '1089-7690']}
]


def make_unique(lista):
  """Keep only unique elements of list
  """
  f = []
  for it in lista:
    if it not in f:
      f.append(it)
  return f


def is_string_like(obj):
  'Return True if *obj* looks like a string (from matplotlib)'
  if isinstance(obj, str):
    return True
  try:
    obj + ''
  except BaseException:
    return False
  return True


def isIterable(obj):
  try:
    iter(obj)
  except BaseException:
    return False
  return True


def is_writable_file_like(obj):
  '''return true if *obj* looks like a file object with a *write* method (from matplotlib)'''
  return hasattr(obj, 'write') and isinstance(obj.write, collections.Callable)


def is_readable_file_like(obj):  # No se si funciona
  '''return true if *obj* looks like a file object with a *read* method '''
  return hasattr(obj, 'read') and isinstance(obj.read, collections.Callable)


def to_filehandle(fname, flag='r', return_opened=False, encoding=None):
  """ from matplotlib
  *fname* can be a filename or a file handle.  Support for gzipped
  files is automatic, if the filename ends in .gz.  *flag* is a
  read/write flag for :func:`file`
  """
  if is_string_like(fname):
    fh = Path(fname).open(mode=flag)
    opened = True
  elif isinstance(fname, Path):
    fh = fname.open(mode=flag)
  elif hasattr(fname, 'seek'):
    fh = fname
    opened = False
  else:
    raise ValueError('fname must be a pathlib Path, string or file handle')
  if return_opened:
    return fh, opened
  return fh


def tsplit(s, sep):
  """Splits a string like split but allows for several char delimiters
  Example:
  >>> tsplit("hello,my|name+is-bob", (",", "|", "+", "-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", ",|+-")
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", list(",|+-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", "+")
  ['hello,my|name', 'is-bob']

  Arguments:
  - `s`: string to separate
  - `sep`: separators
  """
  stack = [s]
  for char in sep:
    pieces = []
    for substr in stack:
      pieces.extend(substr.split(char))
    stack = pieces
  return stack


def openfile(fname=None, intent='r', encoding=None):
  if fname is None or fname == '-' or fname == '':
    if intent == 'r':
      fi = sys.stdin
    else:
      fi = sys.stdout
    return fi

  return to_filehandle(fname=fname, flag=intent, encoding=encoding)


reg_simplify = re.compile(r'\W')


def oversimplify(strng):
  """
  (Over)simplify a string by converting to latex and then removing everything that does not belong to a word
  """
  s = strng.encode('latex').decode('utf-8')
  s = reg_simplify.sub('', s)
  return s


def closefile(fi):
  if fi != sys.stdin and fi != sys.stdout and fi != sys.stderr:
    fi.close()


reg_defstrng = re.compile(
    '[{]*DEFINITIONOFSTRING[(](\\w+)[)][{]*(\\s*[#]*\\s*["]*\\s*\\w*\\s*["]*\\s*)[}]*', re.I)
# reg_defstrng= re.compile('[{]*DEFINITIONOFSTRING[(](\w+)[)][}]*',re.I)


def verify_entry(b):
  """Verify that the entry is valid (minimal fields are present)

  Parameters
  ----------
  b : dict
    entry values

  Returns
  -------
  bool:
    True if is valid
  """
  for f in minimalfields:
    if f not in b:
      return False
  return True


def add_journal_abbrev(b):
  """Try to add journal abbreviation

  Parameters
  ----------
  b : dict
    entry values
  """
  if 'journal' in b:
    if 'journal_abbrev' not in b:
      # Create one abbrev
      journal, abbrev = identify_some_journals(b)
      b['journal_abbrev'] = abbrev
      b['journal'] = journal


def replace_abbrevs(strs, sourcestrng):
  if 'DEFINITIONOFSTRING' in sourcestrng or 'definitionofstring' in sourcestrng:
    v = sourcestrng
    for abbrev, defin in list(strs.items()):
      v = v.replace('DEFINITIONOFSTRING(%s)' % (abbrev), defin)
      v = v.replace('DEFINITIONOFSTRING(%s)' % (abbrev).lower(), defin)
      v = v.replace('DEFINITIONOFSTRING(%s)' % (abbrev).upper(), defin)
      v = v.replace('definitionofstring(%s)' % (abbrev), defin)
      v = v.replace('definitionofstring(%s)' % (abbrev).lower(), defin)
      v = v.replace('definitionofstring(%s)' % (abbrev).upper(), defin)
      d = ' '.join([x.strip().strip('"') for x in v.split('#')])
    return d
  return sourcestrng


def process_name(name):
  """
  Process one name and separate it in '[von, Last, First, Jr]'
  """
  def getnames_form3(a):
    """
    Case with two commas: the name is of the format
    von Last, Jr, First
    like in: von Hicks, III, Michael
    """
    full_last = a[0].strip()
    full_first = a[2].strip()
    junior = a[1].strip()
    von, last = get_vonlast(full_last)
    return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

  def getnames_form2(a):
    """
    Case with one comma: the name is of the format
    von Last, First
    like in: von Hicks, Michael
    """
    full_last = a[0].strip()
    full_first = a[1].strip()
    junior = ''
    von, last = get_vonlast(full_last)
    return [von.strip(), last.strip(), full_first.strip(), junior]

  def getnames_form1(a):
    """
    Case with NO commas: the name is of the format
    First von Last
    like in: Michael von Hicks
    """
    last = a[0].split(' ')
    nfn = 0
    for l in last:
      if l != "" and not l[0].islower():
        nfn += 1
      else:
        break
    if nfn == len(last):
      nfn = -1

    full_first = ' '.join(last[:nfn])
    full_first = full_first.replace('.', ' ')
    full_last = ' '.join(last[nfn:])
    junior = " "
    von, last = get_vonlast(full_last)
    return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

  def get_vonlast(full_last):
    von = ""
    last = ""

    for l in full_last.split(' '):
      if len(l) > 0 and l[0].islower():
        von += l.lower() + " "
      else:
        last += l + " "
    return von, last

  # Start the processing
  a = name.split(',')
  if len(a) == 3:
    fullname = getnames_form3(a)
  elif len(a) == 2:
    fullname = getnames_form2(a)
  elif len(a) == 1:
    fullname = getnames_form1(a)
  else:
    fullname = []

  return fullname


def create_initials(strng):
  return ''.join([x for x in [x[0] for x in strng.split()] if x.isupper()])


def identify_some_journals(bibitem, known_journals=journal_data):

  # First try to identify the ISSN
  if 'issn' in bibitem:
    for j in known_journals:
      if bibitem['issn'] == j['issn']:
        return j['nombre'], j['abbrev']

  # Then the DOI
  if 'doi' in bibitem:
    for j in known_journals:
      if bibitem['doi'].find(j['doi']) != -1:
        if j['issn'] != []:
          bibitem['issn'] = j['issn'][0]
        return j['nombre'], j['abbrev']

  # If DOI does not work, identify the journal with the regexp
  if 'journal' in bibitem:
    for j in known_journals:
      if re.search(j['regexp'], bibitem['journal']) is not None:
        if j['issn'] != []:
          bibitem['issn'] = j['issn'][0]
        return j['nombre'], j['abbrev']

    # If it is not a know journal, get the abbreviation from the first letters
    # of each word
    abbrev = create_initials(bibitem['journal'])
    return bibitem['journal'], abbrev
  else:
    # If nothing worked return emtpy strings
    return '', ''


def mathmode(strng):
  """Agrega math mode a titulos y abstracts"""
  mathexp = (
      # forma: a^{b}
      re.compile(r"([\^])[{]([^}\$]+)[}]"), re.compile(r"([_])[{]([^}$]+)[}]"),
      # forma: a^\beta
      re.compile(r"([\^])(\\[\w]+)"), re.compile(r"([_])(\\[\w]+)"),
      # forma: a^b
      re.compile(r"([\^])([^\{\\\$])"), re.compile(r"([_])([^\$\{\\])")
  )
  for i in mathexp:
    strng = i.sub(r"$\1{\2}$", strng)
  return strng


def add_math(entry):
  """
  Agrega el símbolo de $ donde sea necesario (si?)
  Recibe una entrada completa (abstract, title, etc)
  """
  p = re.compile(r'(\$[^\$]+\$)')  # Find math substrngings
  L = p.split(entry)
  s = ""
  for k in L:
    if "$" not in k:
      s += mathmode(k)
    else:
      s += k
  return s


def handle_math(strng, orden=0):
  """
  Convierte entre tex <--> html.
  Si orden =0 => bib -> html
  Si orden =1 => html -> bib
  """
  mathexp = ([(re.compile(r'\^([^{])', re.I), r'<sup>\1</sup>'), (re.compile(r'\^{([^{]+)}', re.I), r'<sup>\1</sup>'), (re.compile(r'_([^{]+){', re.I), r'<sub>\1</sub>'), (re.compile(r'_{([^{]+)}', re.I), r'<sub>\1</sub>'), (re.compile(r'\\mathrm{([^{]+)}', re.I), r'{\1}')
              ],
             [(re.compile(r'<sub>([^<]*)</sub>', re.I), r'$_{\1}$'), (re.compile(r'<sup>([^<]*)</sup>', re.I), r'$^{\1}$')
              ])
#   mathmarker= ('<math>','</math>')
  mathmarker = ('', '')

  if orden == 0:
    p = re.compile(r'\$([^\$]+)\$')  # Find math substrngings
    if p.search(strng):
      ini = 0
      linecontent = ''
      iterator = p.finditer(strng)
      for match in iterator:
        strngmath = match.group()[1:-1]
        linecontent += strng[ini:match.start()]
        for i, o in mathexp[orden]:
          strngmath = re.sub(i, o, strngmath)
        linecontent += mathmarker[0] + strngmath + mathmarker[1]
        ini = match.end()
      linecontent += strng[ini:]
    else:
      return strng
  else:
    for i, o in mathexp[orden]:
      strng = i.sub(o, strng)
    linecontent = strng

  return linecontent


# return the string parameter without braces
#
rembraces_rex = re.compile('[{}]')


def removebraces(str):
  return rembraces_rex.sub('', str)


capitalize_rex = re.compile(r'({\w*})')
cap_rex = re.compile('{([a-zA-Z]*)}')


def capitalizestring(data):
  """Capitalizes a string preserving character that are between braces

  Parameters
  ----------
  data = title string

  Returns
  -------
  string:
    the capitalized string (first letter is capitalized), rest are capitalized
    only if capitalized inside braces
  """
  ss_list = capitalize_rex.split(data)
  ss = ''
  count = 0
  for phrase in ss_list:
    check = phrase.lstrip()

    # keep phrase's capitalization the same
    if check.find('{') == 0:
      ss += cap_rex.sub(r'\1', phrase)
    else:
      # first word --> capitalize first letter (after spaces)
      if count == 0:
        ss += phrase.capitalize()
      else:
        ss += phrase.lower()
      count += 1
  return ss


def match_pair(expr, pair=(r'{', r'}'), start=0):
  """
  Find the outermost pair enclosing a given expresion

  pair is a 2-tuple containing (begin, end) where both may be characters or strings
  for instance:
    pair= ('[',']')  or
    pair= ('if','end if') or
    pair= ('<div>','</div>') or ...

    """

  beg = pair[0]
  fin = pair[1]

  #   find first opening
  sstart = expr.find(beg, start)

  count = 0

  if beg == fin:
    eend = expr.find(fin, sstart + 1)
    return sstart, eend

  p = re.compile('(' + beg + '|' + fin + ')', re.M)
  ps = re.compile(beg, re.M)

  iterator = p.finditer(expr, start)

  for match in iterator:
    if ps.match(match.group()):
      count += 1
    else:
      count += -1

    if count == 0:
      return sstart, match.end()

  return None


def no_outer_parens(filecontents):
  # JF: TODO We should check/change this routine Convierte los
  # parentesis a llaves cuando contienen el item completo.
  # Do checking for open parens will convert to braces
  paren_split = re.split('([(){}])', filecontents)

  open_paren_count = 0
  open_type = False
  look_next = False

  # rebuild filecontents
  filecontents = ''

  at_rex = re.compile(r'@\w*')

  for phrase in paren_split:
    if look_next:
      if phrase == '(':
        phrase = '{'
        open_paren_count += 1
      else:
        open_type = False
      look_next = False

    if phrase == '(':
      open_paren_count += 1
    elif phrase == ')':
      open_paren_count -= 1
      if open_type and open_paren_count == 0:
        phrase = '}'
        open_type = False
    elif at_rex.search(phrase):
      open_type = True
      look_next = True

    filecontents = filecontents + phrase
  return filecontents


def replace_tags(strng, what='All'):
  ww = what.lower()
  if ww == 'all' or ww == 'xml':
    # encode character entities
    for i, o in xml_tags:
      strng = strng.replace(i, o)

  if ww == 'all' or ww == 'accents':
    # latex-specific character replacements
    for i, o in accent_tags:
      # strng = string.replace(strng, i, o)
      strng = strng.replace(i, o)

  if ww == 'all' or ww == 'other':
    # Other LaTeX tags, handled by regexps
    for i, o in other_tags:
      strng = re.sub(i, o, strng)

  return strng

# def filter_author(biblist, filter_options):
#   delete=[]
#   for ident,item in biblist.iteritems():
#     valid = True
#     if item.has_key('author'):
#       aut = [ ' '.join(k) for k in item['author']]
#     else:
#       print ident, item
#       sys.exit(1)


# def filter_bib(biblist, filter_options):
#   delete=[]
#   for ident,item in biblist.iteritems():
#     valid = True
#     for k,v,cond in filter_options :
#       valid= item.has_key(k) and valid
#       if valid:
#         if type(item[k]) == list:
#           if k == 'author':
#             aut = [ ' '.join( s ) for s in item['author']]
#             data= ' '.join( aut )
#           else:
#             data= join( item[k] )
#         else:
#           data= item[k]
#         valid = valid and ((v in data.lower()) == cond)

#         if not valid:
#           delete.append(ident)
#           break
#       else:
#         delete.append(ident)
#         break

#   # Collect all matching and return them
#   bib={}
#   for ident in biblist.keys():
#     if ident not in delete:
#       bib[ident]= biblist[ident].copy()

#   return bib

d3128f2701fc96c43065e711fa3d899ed69116fd
<mask token>


def make_unique(lista):
    """Keep only unique elements of list
  """
    f = []
    for it in lista:
        if it not in f:
            f.append(it)
    return f


def is_string_like(obj):
    """Return True if *obj* looks like a string (from matplotlib)"""
    if isinstance(obj, str):
        return True
    try:
        obj + ''
    except BaseException:
        return False
    return True


<mask token>


def is_writable_file_like(obj):
    """return true if *obj* looks like a file object with a *write* method (from matplotlib)"""
    return hasattr(obj, 'write') and isinstance(obj.write, collections.Callable
        )


def is_readable_file_like(obj):
    """return true if *obj* looks like a file object with a *read* method """
    return hasattr(obj, 'read') and isinstance(obj.read, collections.Callable)


def to_filehandle(fname, flag='r', return_opened=False, encoding=None):
    """ from matplotlib
  *fname* can be a filename or a file handle.  Support for gzipped
  files is automatic, if the filename ends in .gz.  *flag* is a
  read/write flag for :func:`file`
  """
    if is_string_like(fname):
        fh = Path(fname).open(mode=flag)
        opened = True
    elif isinstance(fname, Path):
        fh = fname.open(mode=flag)
    elif hasattr(fname, 'seek'):
        fh = fname
        opened = False
    else:
        raise ValueError('fname must be a pathlib Path, string or file handle')
    if return_opened:
        return fh, opened
    return fh


def tsplit(s, sep):
    """Splits a string like split but allows for several char delimiters
  Example:
  >>> tsplit("hello,my|name+is-bob", (",", "|", "+", "-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", ",|+-")
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", list(",|+-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", "+")
  ['hello,my|name', 'is-bob']

  Arguments:
  - `s`: string to separate
  - `sep`: separators
  """
    stack = [s]
    for char in sep:
        pieces = []
        for substr in stack:
            pieces.extend(substr.split(char))
        stack = pieces
    return stack


def openfile(fname=None, intent='r', encoding=None):
    if fname is None or fname == '-' or fname == '':
        if intent == 'r':
            fi = sys.stdin
        else:
            fi = sys.stdout
        return fi
    return to_filehandle(fname=fname, flag=intent, encoding=encoding)


<mask token>


def oversimplify(strng):
    """
  (Over)simplify a string by converting to latex and then removing everything that does not belong to a word
  """
    s = strng.encode('latex').decode('utf-8')
    s = reg_simplify.sub('', s)
    return s


def closefile(fi):
    if fi != sys.stdin and fi != sys.stdout and fi != sys.stderr:
        fi.close()


<mask token>


def verify_entry(b):
    """Verify that the entry is valid (minimal fields are present)

  Parameters
  ----------
  b : dict
    entry values

  Returns
  -------
  bool:
    True if is valid
  """
    for f in minimalfields:
        if f not in b:
            return False
    return True


def add_journal_abbrev(b):
    """Try to add journal abbreviation

  Parameters
  ----------
  b : dict
    entry values
  """
    if 'journal' in b:
        if 'journal_abbrev' not in b:
            journal, abbrev = identify_some_journals(b)
            b['journal_abbrev'] = abbrev
            b['journal'] = journal


<mask token>


def process_name(name):
    """
  Process one name and separate it in '[von, Last, First, Jr]'
  """

    def getnames_form3(a):
        """
    Case with two commas: the name is of the format
    von Last, Jr, First
    like in: von Hicks, III, Michael
    """
        full_last = a[0].strip()
        full_first = a[2].strip()
        junior = a[1].strip()
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

    def getnames_form2(a):
        """
    Case with one comma: the name is of the format
    von Last, First
    like in: von Hicks, Michael
    """
        full_last = a[0].strip()
        full_first = a[1].strip()
        junior = ''
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior]

    def getnames_form1(a):
        """
    Case with NO commas: the name is of the format
    First von Last
    like in: Michael von Hicks
    """
        last = a[0].split(' ')
        nfn = 0
        for l in last:
            if l != '' and not l[0].islower():
                nfn += 1
            else:
                break
        if nfn == len(last):
            nfn = -1
        full_first = ' '.join(last[:nfn])
        full_first = full_first.replace('.', ' ')
        full_last = ' '.join(last[nfn:])
        junior = ' '
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

    def get_vonlast(full_last):
        von = ''
        last = ''
        for l in full_last.split(' '):
            if len(l) > 0 and l[0].islower():
                von += l.lower() + ' '
            else:
                last += l + ' '
        return von, last
    a = name.split(',')
    if len(a) == 3:
        fullname = getnames_form3(a)
    elif len(a) == 2:
        fullname = getnames_form2(a)
    elif len(a) == 1:
        fullname = getnames_form1(a)
    else:
        fullname = []
    return fullname


<mask token>


def identify_some_journals(bibitem, known_journals=journal_data):
    if 'issn' in bibitem:
        for j in known_journals:
            if bibitem['issn'] == j['issn']:
                return j['nombre'], j['abbrev']
    if 'doi' in bibitem:
        for j in known_journals:
            if bibitem['doi'].find(j['doi']) != -1:
                if j['issn'] != []:
                    bibitem['issn'] = j['issn'][0]
                return j['nombre'], j['abbrev']
    if 'journal' in bibitem:
        for j in known_journals:
            if re.search(j['regexp'], bibitem['journal']) is not None:
                if j['issn'] != []:
                    bibitem['issn'] = j['issn'][0]
                return j['nombre'], j['abbrev']
        abbrev = create_initials(bibitem['journal'])
        return bibitem['journal'], abbrev
    else:
        return '', ''


def mathmode(strng):
    """Agrega math mode a titulos y abstracts"""
    mathexp = re.compile('([\\^])[{]([^}\\$]+)[}]'), re.compile(
        '([_])[{]([^}$]+)[}]'), re.compile('([\\^])(\\\\[\\w]+)'), re.compile(
        '([_])(\\\\[\\w]+)'), re.compile('([\\^])([^\\{\\\\\\$])'), re.compile(
        '([_])([^\\$\\{\\\\])')
    for i in mathexp:
        strng = i.sub('$\\1{\\2}$', strng)
    return strng


def add_math(entry):
    """
  Agrega el símbolo de $ donde sea necesario (si?)
  Recibe una entrada completa (abstract, title, etc)
  """
    p = re.compile('(\\$[^\\$]+\\$)')
    L = p.split(entry)
    s = ''
    for k in L:
        if '$' not in k:
            s += mathmode(k)
        else:
            s += k
    return s


def handle_math(strng, orden=0):
    """
  Convierte entre tex <--> html.
  Si orden =0 => bib -> html
  Si orden =1 => html -> bib
  """
    mathexp = [(re.compile('\\^([^{])', re.I), '<sup>\\1</sup>'), (re.
        compile('\\^{([^{]+)}', re.I), '<sup>\\1</sup>'), (re.compile(
        '_([^{]+){', re.I), '<sub>\\1</sub>'), (re.compile('_{([^{]+)}', re
        .I), '<sub>\\1</sub>'), (re.compile('\\\\mathrm{([^{]+)}', re.I),
        '{\\1}')], [(re.compile('<sub>([^<]*)</sub>', re.I), '$_{\\1}$'), (
        re.compile('<sup>([^<]*)</sup>', re.I), '$^{\\1}$')]
    mathmarker = '', ''
    if orden == 0:
        p = re.compile('\\$([^\\$]+)\\$')
        if p.search(strng):
            ini = 0
            linecontent = ''
            iterator = p.finditer(strng)
            for match in iterator:
                strngmath = match.group()[1:-1]
                linecontent += strng[ini:match.start()]
                for i, o in mathexp[orden]:
                    strngmath = re.sub(i, o, strngmath)
                linecontent += mathmarker[0] + strngmath + mathmarker[1]
                ini = match.end()
            linecontent += strng[ini:]
        else:
            return strng
    else:
        for i, o in mathexp[orden]:
            strng = i.sub(o, strng)
        linecontent = strng
    return linecontent


<mask token>


def removebraces(str):
    return rembraces_rex.sub('', str)


<mask token>


def match_pair(expr, pair=('{', '}'), start=0):
    """
  Find the outermost pair enclosing a given expresion

  pair is a 2-tuple containing (begin, end) where both may be characters or strings
  for instance:
    pair= ('[',']')  or
    pair= ('if','end if') or
    pair= ('<div>','</div>') or ...

    """
    beg = pair[0]
    fin = pair[1]
    sstart = expr.find(beg, start)
    count = 0
    if beg == fin:
        eend = expr.find(fin, sstart + 1)
        return sstart, eend
    p = re.compile('(' + beg + '|' + fin + ')', re.M)
    ps = re.compile(beg, re.M)
    iterator = p.finditer(expr, start)
    for match in iterator:
        if ps.match(match.group()):
            count += 1
        else:
            count += -1
        if count == 0:
            return sstart, match.end()
    return None


def no_outer_parens(filecontents):
    paren_split = re.split('([(){}])', filecontents)
    open_paren_count = 0
    open_type = False
    look_next = False
    filecontents = ''
    at_rex = re.compile('@\\w*')
    for phrase in paren_split:
        if look_next:
            if phrase == '(':
                phrase = '{'
                open_paren_count += 1
            else:
                open_type = False
            look_next = False
        if phrase == '(':
            open_paren_count += 1
        elif phrase == ')':
            open_paren_count -= 1
            if open_type and open_paren_count == 0:
                phrase = '}'
                open_type = False
        elif at_rex.search(phrase):
            open_type = True
            look_next = True
        filecontents = filecontents + phrase
    return filecontents


def replace_tags(strng, what='All'):
    ww = what.lower()
    if ww == 'all' or ww == 'xml':
        for i, o in xml_tags:
            strng = strng.replace(i, o)
    if ww == 'all' or ww == 'accents':
        for i, o in accent_tags:
            strng = strng.replace(i, o)
    if ww == 'all' or ww == 'other':
        for i, o in other_tags:
            strng = re.sub(i, o, strng)
    return strng

<mask token>


def make_unique(lista):
    """Keep only unique elements of list
  """
    f = []
    for it in lista:
        if it not in f:
            f.append(it)
    return f


def is_string_like(obj):
    """Return True if *obj* looks like a string (from matplotlib)"""
    if isinstance(obj, str):
        return True
    try:
        obj + ''
    except BaseException:
        return False
    return True


def isIterable(obj):
    try:
        iter(obj)
    except BaseException:
        return False
    return True


def is_writable_file_like(obj):
    """return true if *obj* looks like a file object with a *write* method (from matplotlib)"""
    return hasattr(obj, 'write') and isinstance(obj.write, collections.Callable
        )


def is_readable_file_like(obj):
    """return true if *obj* looks like a file object with a *read* method """
    return hasattr(obj, 'read') and isinstance(obj.read, collections.Callable)


def to_filehandle(fname, flag='r', return_opened=False, encoding=None):
    """ from matplotlib
  *fname* can be a filename or a file handle.  Support for gzipped
  files is automatic, if the filename ends in .gz.  *flag* is a
  read/write flag for :func:`file`
  """
    if is_string_like(fname):
        fh = Path(fname).open(mode=flag)
        opened = True
    elif isinstance(fname, Path):
        fh = fname.open(mode=flag)
    elif hasattr(fname, 'seek'):
        fh = fname
        opened = False
    else:
        raise ValueError('fname must be a pathlib Path, string or file handle')
    if return_opened:
        return fh, opened
    return fh


def tsplit(s, sep):
    """Splits a string like split but allows for several char delimiters
  Example:
  >>> tsplit("hello,my|name+is-bob", (",", "|", "+", "-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", ",|+-")
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", list(",|+-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", "+")
  ['hello,my|name', 'is-bob']

  Arguments:
  - `s`: string to separate
  - `sep`: separators
  """
    stack = [s]
    for char in sep:
        pieces = []
        for substr in stack:
            pieces.extend(substr.split(char))
        stack = pieces
    return stack


def openfile(fname=None, intent='r', encoding=None):
    if fname is None or fname == '-' or fname == '':
        if intent == 'r':
            fi = sys.stdin
        else:
            fi = sys.stdout
        return fi
    return to_filehandle(fname=fname, flag=intent, encoding=encoding)


<mask token>


def oversimplify(strng):
    """
  (Over)simplify a string by converting to latex and then removing everything that does not belong to a word
  """
    s = strng.encode('latex').decode('utf-8')
    s = reg_simplify.sub('', s)
    return s


def closefile(fi):
    if fi != sys.stdin and fi != sys.stdout and fi != sys.stderr:
        fi.close()


<mask token>


def verify_entry(b):
    """Verify that the entry is valid (minimal fields are present)

  Parameters
  ----------
  b : dict
    entry values

  Returns
  -------
  bool:
    True if is valid
  """
    for f in minimalfields:
        if f not in b:
            return False
    return True


def add_journal_abbrev(b):
    """Try to add journal abbreviation

  Parameters
  ----------
  b : dict
    entry values
  """
    if 'journal' in b:
        if 'journal_abbrev' not in b:
            journal, abbrev = identify_some_journals(b)
            b['journal_abbrev'] = abbrev
            b['journal'] = journal


<mask token>


def process_name(name):
    """
  Process one name and separate it in '[von, Last, First, Jr]'
  """

    def getnames_form3(a):
        """
    Case with two commas: the name is of the format
    von Last, Jr, First
    like in: von Hicks, III, Michael
    """
        full_last = a[0].strip()
        full_first = a[2].strip()
        junior = a[1].strip()
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

    def getnames_form2(a):
        """
    Case with one comma: the name is of the format
    von Last, First
    like in: von Hicks, Michael
    """
        full_last = a[0].strip()
        full_first = a[1].strip()
        junior = ''
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior]

    def getnames_form1(a):
        """
    Case with NO commas: the name is of the format
    First von Last
    like in: Michael von Hicks
    """
        last = a[0].split(' ')
        nfn = 0
        for l in last:
            if l != '' and not l[0].islower():
                nfn += 1
            else:
                break
        if nfn == len(last):
            nfn = -1
        full_first = ' '.join(last[:nfn])
        full_first = full_first.replace('.', ' ')
        full_last = ' '.join(last[nfn:])
        junior = ' '
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

    def get_vonlast(full_last):
        von = ''
        last = ''
        for l in full_last.split(' '):
            if len(l) > 0 and l[0].islower():
                von += l.lower() + ' '
            else:
                last += l + ' '
        return von, last
    a = name.split(',')
    if len(a) == 3:
        fullname = getnames_form3(a)
    elif len(a) == 2:
        fullname = getnames_form2(a)
    elif len(a) == 1:
        fullname = getnames_form1(a)
    else:
        fullname = []
    return fullname


<mask token>


def identify_some_journals(bibitem, known_journals=journal_data):
    if 'issn' in bibitem:
        for j in known_journals:
            if bibitem['issn'] == j['issn']:
                return j['nombre'], j['abbrev']
    if 'doi' in bibitem:
        for j in known_journals:
            if bibitem['doi'].find(j['doi']) != -1:
                if j['issn'] != []:
                    bibitem['issn'] = j['issn'][0]
                return j['nombre'], j['abbrev']
    if 'journal' in bibitem:
        for j in known_journals:
            if re.search(j['regexp'], bibitem['journal']) is not None:
                if j['issn'] != []:
                    bibitem['issn'] = j['issn'][0]
                return j['nombre'], j['abbrev']
        abbrev = create_initials(bibitem['journal'])
        return bibitem['journal'], abbrev
    else:
        return '', ''


def mathmode(strng):
    """Agrega math mode a titulos y abstracts"""
    mathexp = re.compile('([\\^])[{]([^}\\$]+)[}]'), re.compile(
        '([_])[{]([^}$]+)[}]'), re.compile('([\\^])(\\\\[\\w]+)'), re.compile(
        '([_])(\\\\[\\w]+)'), re.compile('([\\^])([^\\{\\\\\\$])'), re.compile(
        '([_])([^\\$\\{\\\\])')
    for i in mathexp:
        strng = i.sub('$\\1{\\2}$', strng)
    return strng


def add_math(entry):
    """
  Agrega el símbolo de $ donde sea necesario (si?)
  Recibe una entrada completa (abstract, title, etc)
  """
    p = re.compile('(\\$[^\\$]+\\$)')
    L = p.split(entry)
    s = ''
    for k in L:
        if '$' not in k:
            s += mathmode(k)
        else:
            s += k
    return s


def handle_math(strng, orden=0):
    """
  Convierte entre tex <--> html.
  Si orden =0 => bib -> html
  Si orden =1 => html -> bib
  """
    mathexp = [(re.compile('\\^([^{])', re.I), '<sup>\\1</sup>'), (re.
        compile('\\^{([^{]+)}', re.I), '<sup>\\1</sup>'), (re.compile(
        '_([^{]+){', re.I), '<sub>\\1</sub>'), (re.compile('_{([^{]+)}', re
        .I), '<sub>\\1</sub>'), (re.compile('\\\\mathrm{([^{]+)}', re.I),
        '{\\1}')], [(re.compile('<sub>([^<]*)</sub>', re.I), '$_{\\1}$'), (
        re.compile('<sup>([^<]*)</sup>', re.I), '$^{\\1}$')]
    mathmarker = '', ''
    if orden == 0:
        p = re.compile('\\$([^\\$]+)\\$')
        if p.search(strng):
            ini = 0
            linecontent = ''
            iterator = p.finditer(strng)
            for match in iterator:
                strngmath = match.group()[1:-1]
                linecontent += strng[ini:match.start()]
                for i, o in mathexp[orden]:
                    strngmath = re.sub(i, o, strngmath)
                linecontent += mathmarker[0] + strngmath + mathmarker[1]
                ini = match.end()
            linecontent += strng[ini:]
        else:
            return strng
    else:
        for i, o in mathexp[orden]:
            strng = i.sub(o, strng)
        linecontent = strng
    return linecontent


<mask token>


def removebraces(str):
    return rembraces_rex.sub('', str)


<mask token>


def match_pair(expr, pair=('{', '}'), start=0):
    """
  Find the outermost pair enclosing a given expresion

  pair is a 2-tuple containing (begin, end) where both may be characters or strings
  for instance:
    pair= ('[',']')  or
    pair= ('if','end if') or
    pair= ('<div>','</div>') or ...

    """
    beg = pair[0]
    fin = pair[1]
    sstart = expr.find(beg, start)
    count = 0
    if beg == fin:
        eend = expr.find(fin, sstart + 1)
        return sstart, eend
    p = re.compile('(' + beg + '|' + fin + ')', re.M)
    ps = re.compile(beg, re.M)
    iterator = p.finditer(expr, start)
    for match in iterator:
        if ps.match(match.group()):
            count += 1
        else:
            count += -1
        if count == 0:
            return sstart, match.end()
    return None


def no_outer_parens(filecontents):
    paren_split = re.split('([(){}])', filecontents)
    open_paren_count = 0
    open_type = False
    look_next = False
    filecontents = ''
    at_rex = re.compile('@\\w*')
    for phrase in paren_split:
        if look_next:
            if phrase == '(':
                phrase = '{'
                open_paren_count += 1
            else:
                open_type = False
            look_next = False
        if phrase == '(':
            open_paren_count += 1
        elif phrase == ')':
            open_paren_count -= 1
            if open_type and open_paren_count == 0:
                phrase = '}'
                open_type = False
        elif at_rex.search(phrase):
            open_type = True
            look_next = True
        filecontents = filecontents + phrase
    return filecontents


def replace_tags(strng, what='All'):
    ww = what.lower()
    if ww == 'all' or ww == 'xml':
        for i, o in xml_tags:
            strng = strng.replace(i, o)
    if ww == 'all' or ww == 'accents':
        for i, o in accent_tags:
            strng = strng.replace(i, o)
    if ww == 'all' or ww == 'other':
        for i, o in other_tags:
            strng = re.sub(i, o, strng)
    return strng

<mask token>


def make_unique(lista):
    """Keep only unique elements of list
  """
    f = []
    for it in lista:
        if it not in f:
            f.append(it)
    return f


def is_string_like(obj):
    """Return True if *obj* looks like a string (from matplotlib)"""
    if isinstance(obj, str):
        return True
    try:
        obj + ''
    except BaseException:
        return False
    return True


def isIterable(obj):
    try:
        iter(obj)
    except BaseException:
        return False
    return True


def is_writable_file_like(obj):
    """return true if *obj* looks like a file object with a *write* method (from matplotlib)"""
    return hasattr(obj, 'write') and isinstance(obj.write, collections.Callable
        )


def is_readable_file_like(obj):
    """return true if *obj* looks like a file object with a *read* method """
    return hasattr(obj, 'read') and isinstance(obj.read, collections.Callable)


def to_filehandle(fname, flag='r', return_opened=False, encoding=None):
    """ from matplotlib
  *fname* can be a filename or a file handle.  Support for gzipped
  files is automatic, if the filename ends in .gz.  *flag* is a
  read/write flag for :func:`file`
  """
    if is_string_like(fname):
        fh = Path(fname).open(mode=flag)
        opened = True
    elif isinstance(fname, Path):
        fh = fname.open(mode=flag)
    elif hasattr(fname, 'seek'):
        fh = fname
        opened = False
    else:
        raise ValueError('fname must be a pathlib Path, string or file handle')
    if return_opened:
        return fh, opened
    return fh


def tsplit(s, sep):
    """Splits a string like split but allows for several char delimiters
  Example:
  >>> tsplit("hello,my|name+is-bob", (",", "|", "+", "-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", ",|+-")
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", list(",|+-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", "+")
  ['hello,my|name', 'is-bob']

  Arguments:
  - `s`: string to separate
  - `sep`: separators
  """
    stack = [s]
    for char in sep:
        pieces = []
        for substr in stack:
            pieces.extend(substr.split(char))
        stack = pieces
    return stack


def openfile(fname=None, intent='r', encoding=None):
    if fname is None or fname == '-' or fname == '':
        if intent == 'r':
            fi = sys.stdin
        else:
            fi = sys.stdout
        return fi
    return to_filehandle(fname=fname, flag=intent, encoding=encoding)


<mask token>


def oversimplify(strng):
    """
  (Over)simplify a string by converting to latex and then removing everything that does not belong to a word
  """
    s = strng.encode('latex').decode('utf-8')
    s = reg_simplify.sub('', s)
    return s


def closefile(fi):
    if fi != sys.stdin and fi != sys.stdout and fi != sys.stderr:
        fi.close()


<mask token>


def verify_entry(b):
    """Verify that the entry is valid (minimal fields are present)

  Parameters
  ----------
  b : dict
    entry values

  Returns
  -------
  bool:
    True if is valid
  """
    for f in minimalfields:
        if f not in b:
            return False
    return True


def add_journal_abbrev(b):
    """Try to add journal abbreviation

  Parameters
  ----------
  b : dict
    entry values
  """
    if 'journal' in b:
        if 'journal_abbrev' not in b:
            journal, abbrev = identify_some_journals(b)
            b['journal_abbrev'] = abbrev
            b['journal'] = journal


<mask token>


def process_name(name):
    """
  Process one name and separate it in '[von, Last, First, Jr]'
  """

    def getnames_form3(a):
        """
    Case with two commas: the name is of the format
    von Last, Jr, First
    like in: von Hicks, III, Michael
    """
        full_last = a[0].strip()
        full_first = a[2].strip()
        junior = a[1].strip()
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

    def getnames_form2(a):
        """
    Case with one comma: the name is of the format
    von Last, First
    like in: von Hicks, Michael
    """
        full_last = a[0].strip()
        full_first = a[1].strip()
        junior = ''
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior]

    def getnames_form1(a):
        """
    Case with NO commas: the name is of the format
    First von Last
    like in: Michael von Hicks
    """
        last = a[0].split(' ')
        nfn = 0
        for l in last:
            if l != '' and not l[0].islower():
                nfn += 1
            else:
                break
        if nfn == len(last):
            nfn = -1
        full_first = ' '.join(last[:nfn])
        full_first = full_first.replace('.', ' ')
        full_last = ' '.join(last[nfn:])
        junior = ' '
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

    def get_vonlast(full_last):
        von = ''
        last = ''
        for l in full_last.split(' '):
            if len(l) > 0 and l[0].islower():
                von += l.lower() + ' '
            else:
                last += l + ' '
        return von, last
    a = name.split(',')
    if len(a) == 3:
        fullname = getnames_form3(a)
    elif len(a) == 2:
        fullname = getnames_form2(a)
    elif len(a) == 1:
        fullname = getnames_form1(a)
    else:
        fullname = []
    return fullname


<mask token>


def identify_some_journals(bibitem, known_journals=journal_data):
    if 'issn' in bibitem:
        for j in known_journals:
            if bibitem['issn'] == j['issn']:
                return j['nombre'], j['abbrev']
    if 'doi' in bibitem:
        for j in known_journals:
            if bibitem['doi'].find(j['doi']) != -1:
                if j['issn'] != []:
                    bibitem['issn'] = j['issn'][0]
                return j['nombre'], j['abbrev']
    if 'journal' in bibitem:
        for j in known_journals:
            if re.search(j['regexp'], bibitem['journal']) is not None:
                if j['issn'] != []:
                    bibitem['issn'] = j['issn'][0]
                return j['nombre'], j['abbrev']
        abbrev = create_initials(bibitem['journal'])
        return bibitem['journal'], abbrev
    else:
        return '', ''


def mathmode(strng):
    """Agrega math mode a titulos y abstracts"""
    mathexp = re.compile('([\\^])[{]([^}\\$]+)[}]'), re.compile(
        '([_])[{]([^}$]+)[}]'), re.compile('([\\^])(\\\\[\\w]+)'), re.compile(
        '([_])(\\\\[\\w]+)'), re.compile('([\\^])([^\\{\\\\\\$])'), re.compile(
        '([_])([^\\$\\{\\\\])')
    for i in mathexp:
        strng = i.sub('$\\1{\\2}$', strng)
    return strng


def add_math(entry):
    """
  Agrega el símbolo de $ donde sea necesario (si?)
  Recibe una entrada completa (abstract, title, etc)
  """
    p = re.compile('(\\$[^\\$]+\\$)')
    L = p.split(entry)
    s = ''
    for k in L:
        if '$' not in k:
            s += mathmode(k)
        else:
            s += k
    return s


def handle_math(strng, orden=0):
    """
  Convierte entre tex <--> html.
  Si orden =0 => bib -> html
  Si orden =1 => html -> bib
  """
    mathexp = [(re.compile('\\^([^{])', re.I), '<sup>\\1</sup>'), (re.
        compile('\\^{([^{]+)}', re.I), '<sup>\\1</sup>'), (re.compile(
        '_([^{]+){', re.I), '<sub>\\1</sub>'), (re.compile('_{([^{]+)}', re
        .I), '<sub>\\1</sub>'), (re.compile('\\\\mathrm{([^{]+)}', re.I),
        '{\\1}')], [(re.compile('<sub>([^<]*)</sub>', re.I), '$_{\\1}$'), (
        re.compile('<sup>([^<]*)</sup>', re.I), '$^{\\1}$')]
    mathmarker = '', ''
    if orden == 0:
        p = re.compile('\\$([^\\$]+)\\$')
        if p.search(strng):
            ini = 0
            linecontent = ''
            iterator = p.finditer(strng)
            for match in iterator:
                strngmath = match.group()[1:-1]
                linecontent += strng[ini:match.start()]
                for i, o in mathexp[orden]:
                    strngmath = re.sub(i, o, strngmath)
                linecontent += mathmarker[0] + strngmath + mathmarker[1]
                ini = match.end()
            linecontent += strng[ini:]
        else:
            return strng
    else:
        for i, o in mathexp[orden]:
            strng = i.sub(o, strng)
        linecontent = strng
    return linecontent


<mask token>


def removebraces(str):
    return rembraces_rex.sub('', str)


<mask token>


def capitalizestring(data):
    """Capitalizes a string preserving character that are between braces

  Parameters
  ----------
  data = title string

  Returns
  -------
  string:
    the capitalized string (first letter is capitalized), rest are capitalized
    only if capitalized inside braces
  """
    ss_list = capitalize_rex.split(data)
    ss = ''
    count = 0
    for phrase in ss_list:
        check = phrase.lstrip()
        if check.find('{') == 0:
            ss += cap_rex.sub('\\1', phrase)
        else:
            if count == 0:
                ss += phrase.capitalize()
            else:
                ss += phrase.lower()
            count += 1
    return ss


def match_pair(expr, pair=('{', '}'), start=0):
    """
  Find the outermost pair enclosing a given expresion

  pair is a 2-tuple containing (begin, end) where both may be characters or strings
  for instance:
    pair= ('[',']')  or
    pair= ('if','end if') or
    pair= ('<div>','</div>') or ...

    """
    beg = pair[0]
    fin = pair[1]
    sstart = expr.find(beg, start)
    count = 0
    if beg == fin:
        eend = expr.find(fin, sstart + 1)
        return sstart, eend
    p = re.compile('(' + beg + '|' + fin + ')', re.M)
    ps = re.compile(beg, re.M)
    iterator = p.finditer(expr, start)
    for match in iterator:
        if ps.match(match.group()):
            count += 1
        else:
            count += -1
        if count == 0:
            return sstart, match.end()
    return None


def no_outer_parens(filecontents):
    paren_split = re.split('([(){}])', filecontents)
    open_paren_count = 0
    open_type = False
    look_next = False
    filecontents = ''
    at_rex = re.compile('@\\w*')
    for phrase in paren_split:
        if look_next:
            if phrase == '(':
                phrase = '{'
                open_paren_count += 1
            else:
                open_type = False
            look_next = False
        if phrase == '(':
            open_paren_count += 1
        elif phrase == ')':
            open_paren_count -= 1
            if open_type and open_paren_count == 0:
                phrase = '}'
                open_type = False
        elif at_rex.search(phrase):
            open_type = True
            look_next = True
        filecontents = filecontents + phrase
    return filecontents


def replace_tags(strng, what='All'):
    ww = what.lower()
    if ww == 'all' or ww == 'xml':
        for i, o in xml_tags:
            strng = strng.replace(i, o)
    if ww == 'all' or ww == 'accents':
        for i, o in accent_tags:
            strng = strng.replace(i, o)
    if ww == 'all' or ww == 'other':
        for i, o in other_tags:
            strng = re.sub(i, o, strng)
    return strng

import re
import string
import sys
import collections
from pathlib import Path
bibtexfields = ['abstract', 'address', 'affiliation', 'annote', 'author',
    'booktitle', 'chapter', 'crossref', 'day', 'doi', 'edition', 'editor',
    'file', 'howpublished', 'institution', 'isbn', 'issn', 'journal',
    'keywords', 'month', 'note', 'number', 'organization', 'pages',
    'publisher', 'school', 'series', 'title', 'url', 'volume', 'year']
textualfields = ['_code', 'abstract', 'address', 'annote', 'booktitle',
    'chapter', 'crossref', 'doi', 'edition', 'file', 'howpublished',
    'institution', 'isbn', 'issn', 'journal', 'keywords', 'month', 'note',
    'number', 'organization', 'publisher', 'school', 'series', 'title',
    'url', 'volume', 'year']
otherfields = ['_code', '_type', 'code', 'date-added', 'date-modified',
    'daynote', 'firstpage', 'journal_abbrev', 'lastpage']
allfields = sorted(bibtexfields + otherfields)
minimalfields = '_type', 'author'
namefields = ['author', 'editor']
nowrapfields = ['url', 'doi', 'isbn', 'issn', 'crossref']
alltypes = ('article', 'book', 'booklet', 'conference', 'inbook',
    'incollection', 'inproceedings', 'manual', 'mastersthesis', 'misc',
    'phdthesis', 'proceedings', 'techreport', 'unpublished')
strings_month = [('jan', 'January'), ('feb', 'February'), ('mar', 'March'),
    ('apr', 'April'), ('may', 'May'), ('jun', 'June'), ('jul', 'July'), (
    'aug', 'August'), ('sep', 'September'), ('oct', 'October'), ('nov',
    'November'), ('dec', 'December')]
standard_abbrev = strings_month[:]
xml_tags = [('&', '&amp;'), ('<', '&lt;'), ('>', '&gt;')]
accent_tags = [('\\"a', '&#228;'), ('\\`a', '&#224;'), ("\\'a", '&#225;'),
    ('\\~a', '&#227;'), ('\\^a', '&#226;'), ('\\"e', '&#235;'), ('\\`e',
    '&#232;'), ("\\'e", '&#233;'), ('\\^e', '&#234;'), ('\\"\\i', '&#239;'),
    ('\\`\\i', '&#236;'), ("\\'\\i", '&#237;'), ('\\^\\i', '&#238;'), (
    '\\"i', '&#239;'), ('\\`i', '&#236;'), ("\\'i", '&#237;'), ('\\^i',
    '&#238;'), ('\\"o', '&#246;'), ('\\`o', '&#242;'), ("\\'o", '&#243;'),
    ('\\~o', '&#245;'), ('\\^o', '&#244;'), ('\\"u', '&#252;'), ('\\`',
    '&#249;'), ("\\'", '&#250;'), ('\\^', '&#251;'), ('\\"A', '&#196;'), (
    '\\`A', '&#192;'), ("\\'A", '&#193;'), ('\\~A', '&#195;'), ('\\^A',
    '&#194;'), ('\\"E', '&#203;'), ('\\`E', '&#200;'), ("\\'E", '&#201;'),
    ('\\^E', '&#202;'), ('\\"I', '&#207;'), ('\\`I', '&#204;'), ("\\'I",
    '&#205;'), ('\\^I', '&#206;'), ('\\"O', '&#214;'), ('\\`O', '&#210;'),
    ("\\'O", '&#211;'), ('\\~O', '&#213;'), ('\\^O', '&#212;'), ('\\"U',
    '&#220;'), ('\\`', '&#217;'), ("\\'", '&#218;'), ('\\^', '&#219;'), (
    '\\~n', '&#241;'), ('\\~N', '&#209;'), ('\\c c', '&#231;'), ('\\c C',
    '&#199;'), ('\\circ', 'o')]
other_tags = [('([^\\\\])~', '\\g<1> '), ('\\\\textbf{([^{]+)}',
    '<b>\\g<1></b>'), ('\\\\\\\\emph{([^{]+)}', '<em>\\g<1></em>'), (
    '\\\\textit{([^{]+)}', '<i>\\1</i>'), ('\\[^ {]+{(.+)}', '\\g<1> ')]
journal_data = [{'regexp':
    'Nuc\\w*[\\.]?\\s*Inst\\w*[\\.]?\\s*(and)?\\s*Meth\\w*[\\.]?\\s*(in)?\\s*(Phys(ics)?[\\.]?)?\\s*(R(esearch)?[\\.]?)?\\s*(S(ection)?[\\.]?)?\\s*B|nimb'
    , 'nombre': 'Nuclear Instruments and Methods in Physics Research B',
    'abbrev': 'NIMB', 'doi': '10.1016/j.nimb', 'issn': ['0168-583X']}, {
    'regexp':
    'Int(ernational)?[\\.]?\\s*J(our(nal)?)?[\\.]?\\s*(of)?\\s*Q(uantum)?[\\.]?\\s*Ch(em(istry)?)?[\\.]?'
    , 'nombre': 'International Journal of Quantum Chemistry', 'abbrev':
    'IJQC', 'doi': '10.1002/qua', 'issn': ['1097-461X', '0020-7608']}, {
    'regexp':
    'J(ournal)?[\\.]?\\s*(of)?\\s*Phys(ics)?[\\.]?\\s*A(\\s*:\\s*Math(ematical)?\\s*(and)?\\s*(General)?)?|jpa'
    , 'nombre': 'Journal of Physics A: Mathematical and General', 'abbrev':
    'JPA', 'doi': '10.1088/0305-4470', 'issn': ['1751-8113', '1751-8121']},
    {'regexp':
    'J(ournal)?[\\.]?\\s*(of)?\\s*Phys(ics)?[\\.]?\\s*B(\\s*:\\s*At(omic)?[\\.]?\\s*Mol(ecular)?[\\.]?\\s*(and)?\\s*(Opt(ical)?)?\\s*Phys(ics)?)?|jpb'
    , 'nombre':
    'Journal of Physics B: Atomic Molecular and Optical Physics', 'abbrev':
    'JPB', 'doi': '10.1088/0953-4075', 'issn': ['0953-4075', '1361-6455']},
    {'regexp': 'Ph\\w*[\\.]? Rev\\w* A|pra', 'nombre': 'Physical Review A',
    'abbrev': 'PRA', 'doi': '10.1103/PhysRevA', 'issn': ['1050-2947',
    '1094-1622']}, {'regexp': 'Ph\\w*[\\.]? Rew* B|prb', 'nombre':
    'Physical Review B', 'abbrev': 'PRB', 'doi': '10.1103/PhysRevB', 'issn':
    ['1098-0121', '1550-235x']}, {'regexp': 'Ph\\w*[\\.]? Rew* C|prc',
    'nombre': 'Physical Review C', 'abbrev': 'PRC', 'doi':
    '10.1103/PhysRevC', 'issn': ['0556-2813', '1089-490X']}, {'regexp':
    'Ph\\w*[\\.]? Rev\\w*[\\.]? L\\w*[\\.]?|prl', 'nombre':
    'Physical Review Letters', 'abbrev': 'PRL', 'doi':
    '10.1103/PhysRevLett', 'issn': ['0031-9007', '1079-7114']}, {'regexp':
    'Rev\\w*[\\.]?\\s*(of)?\\s*Mod(ern)?\\s*Phys(ics)?|rmp', 'nombre':
    'Review of Modern Physics', 'abbrev': 'RMP', 'doi': 'Not Available',
    'issn': ['0034-6861', '1539-0756']}, {'regexp':
    'J\\w*[\\.]? Phy\\w*[\\.]? Conf\\w*[\\.]?|jcps', 'nombre':
    'Journal of Physics Conference Series', 'abbrev': 'JPCS', 'doi':
    '10.1088/1742-6596', 'issn': ['1742-6588 ', '1742-6596']}, {'regexp':
    'Comp\\w*[\\.]? Phys\\w*[\\.]? C\\w*[\\.]?|cpc', 'nombre':
    'Computer Physics Communications', 'abbrev': 'CPC', 'doi':
    '10.1016/j.cpc', 'issn': ['0010-4655']}, {'regexp':
    'Phys\\w*[\\.]? Scrip\\w*[\\.]?', 'nombre': 'Physica Scripta Volume T',
    'abbrev': 'PS', 'doi': '10.1238/Physica.Topical', 'issn': []}, {
    'regexp': 'Rad\\w*[\\.]? Phy\\w*[\\.]? Chem\\w*[\\.]?|rpc', 'nombre':
    'Radiation Physics and Chemistry', 'abbrev': 'RPC', 'doi':
    '10.1016/j.radphyschem', 'issn': []}, {'regexp':
    'F\\w*[\\.]? Bo\\w*[\\.]? Sys\\w*[\\.]?|fbs', 'nombre':
    'Few-Body Systems', 'abbrev': 'FBS', 'doi': '10.1007/s00601', 'issn': [
    ]}, {'regexp': 'Eur\\w*[\\.]? J\\w*[\\.]? Phys\\w*[\\.]?|ejp', 'nombre':
    'European Journal of Physics', 'abbrev': 'EJP', 'doi': 'Not Available',
    'issn': ['0143-0807', '1361-6404']}, {'regexp':
    'Phys\\w*[\\.]? Let\\w*[\\.]? A|pla', 'nombre': 'Physics Letters A',
    'abbrev': 'PLA', 'doi': 'Not Available', 'issn': ['0375-9601']}, {
    'regexp': 'Am\\w*[\\.]? J\\w*[\\.]? P|ajp', 'nombre':
    'American Journal of Physics', 'abbrev': 'AJP', 'doi': '10.1119/',
    'issn': ['0002-9505']}, {'regexp':
    'J\\w*[\\.]? Chem\\w*[\\.]? Phys\\w*[\\.]?|jcp|JCP', 'nombre':
    'Journal of Chemical Physics', 'abbrev': 'JPC', 'doi': 'Not Available',
    'issn': ['0021-9606', '1089-7690']}]


def make_unique(lista):
    """Keep only unique elements of list
  """
    f = []
    for it in lista:
        if it not in f:
            f.append(it)
    return f


def is_string_like(obj):
    """Return True if *obj* looks like a string (from matplotlib)"""
    if isinstance(obj, str):
        return True
    try:
        obj + ''
    except BaseException:
        return False
    return True


def isIterable(obj):
    try:
        iter(obj)
    except BaseException:
        return False
    return True


def is_writable_file_like(obj):
    """return true if *obj* looks like a file object with a *write* method (from matplotlib)"""
    return hasattr(obj, 'write') and isinstance(obj.write, collections.Callable
        )


def is_readable_file_like(obj):
    """return true if *obj* looks like a file object with a *read* method """
    return hasattr(obj, 'read') and isinstance(obj.read, collections.Callable)


def to_filehandle(fname, flag='r', return_opened=False, encoding=None):
    """ from matplotlib
  *fname* can be a filename or a file handle.  Support for gzipped
  files is automatic, if the filename ends in .gz.  *flag* is a
  read/write flag for :func:`file`
  """
    if is_string_like(fname):
        fh = Path(fname).open(mode=flag)
        opened = True
    elif isinstance(fname, Path):
        fh = fname.open(mode=flag)
    elif hasattr(fname, 'seek'):
        fh = fname
        opened = False
    else:
        raise ValueError('fname must be a pathlib Path, string or file handle')
    if return_opened:
        return fh, opened
    return fh


def tsplit(s, sep):
    """Splits a string like split but allows for several char delimiters
  Example:
  >>> tsplit("hello,my|name+is-bob", (",", "|", "+", "-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", ",|+-")
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", list(",|+-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", "+")
  ['hello,my|name', 'is-bob']

  Arguments:
  - `s`: string to separate
  - `sep`: separators
  """
    stack = [s]
    for char in sep:
        pieces = []
        for substr in stack:
            pieces.extend(substr.split(char))
        stack = pieces
    return stack


def openfile(fname=None, intent='r', encoding=None):
    if fname is None or fname == '-' or fname == '':
        if intent == 'r':
            fi = sys.stdin
        else:
            fi = sys.stdout
        return fi
    return to_filehandle(fname=fname, flag=intent, encoding=encoding)


reg_simplify = re.compile('\\W')


def oversimplify(strng):
    """
  (Over)simplify a string by converting to latex and then removing everything that does not belong to a word
  """
    s = strng.encode('latex').decode('utf-8')
    s = reg_simplify.sub('', s)
    return s


def closefile(fi):
    if fi != sys.stdin and fi != sys.stdout and fi != sys.stderr:
        fi.close()


reg_defstrng = re.compile(
    '[{]*DEFINITIONOFSTRING[(](\\w+)[)][{]*(\\s*[#]*\\s*["]*\\s*\\w*\\s*["]*\\s*)[}]*'
    , re.I)


def verify_entry(b):
    """Verify that the entry is valid (minimal fields are present)

  Parameters
  ----------
  b : dict
    entry values

  Returns
  -------
  bool:
    True if is valid
  """
    for f in minimalfields:
        if f not in b:
            return False
    return True


def add_journal_abbrev(b):
    """Try to add journal abbreviation

  Parameters
  ----------
  b : dict
    entry values
  """
    if 'journal' in b:
        if 'journal_abbrev' not in b:
            journal, abbrev = identify_some_journals(b)
            b['journal_abbrev'] = abbrev
            b['journal'] = journal


def replace_abbrevs(strs, sourcestrng):
    if ('DEFINITIONOFSTRING' in sourcestrng or 'definitionofstring' in
        sourcestrng):
        v = sourcestrng
        for abbrev, defin in list(strs.items()):
            v = v.replace('DEFINITIONOFSTRING(%s)' % abbrev, defin)
            v = v.replace('DEFINITIONOFSTRING(%s)' % abbrev.lower(), defin)
            v = v.replace('DEFINITIONOFSTRING(%s)' % abbrev.upper(), defin)
            v = v.replace('definitionofstring(%s)' % abbrev, defin)
            v = v.replace('definitionofstring(%s)' % abbrev.lower(), defin)
            v = v.replace('definitionofstring(%s)' % abbrev.upper(), defin)
            d = ' '.join([x.strip().strip('"') for x in v.split('#')])
        return d
    return sourcestrng


def process_name(name):
    """
  Process one name and separate it in '[von, Last, First, Jr]'
  """

    def getnames_form3(a):
        """
    Case with two commas: the name is of the format
    von Last, Jr, First
    like in: von Hicks, III, Michael
    """
        full_last = a[0].strip()
        full_first = a[2].strip()
        junior = a[1].strip()
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

    def getnames_form2(a):
        """
    Case with one comma: the name is of the format
    von Last, First
    like in: von Hicks, Michael
    """
        full_last = a[0].strip()
        full_first = a[1].strip()
        junior = ''
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior]

    def getnames_form1(a):
        """
    Case with NO commas: the name is of the format
    First von Last
    like in: Michael von Hicks
    """
        last = a[0].split(' ')
        nfn = 0
        for l in last:
            if l != '' and not l[0].islower():
                nfn += 1
            else:
                break
        if nfn == len(last):
            nfn = -1
        full_first = ' '.join(last[:nfn])
        full_first = full_first.replace('.', ' ')
        full_last = ' '.join(last[nfn:])
        junior = ' '
        von, last = get_vonlast(full_last)
        return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

    def get_vonlast(full_last):
        von = ''
        last = ''
        for l in full_last.split(' '):
            if len(l) > 0 and l[0].islower():
                von += l.lower() + ' '
            else:
                last += l + ' '
        return von, last
    a = name.split(',')
    if len(a) == 3:
        fullname = getnames_form3(a)
    elif len(a) == 2:
        fullname = getnames_form2(a)
    elif len(a) == 1:
        fullname = getnames_form1(a)
    else:
        fullname = []
    return fullname


def create_initials(strng):
    return ''.join([x for x in [x[0] for x in strng.split()] if x.isupper()])


def identify_some_journals(bibitem, known_journals=journal_data):
    if 'issn' in bibitem:
        for j in known_journals:
            if bibitem['issn'] == j['issn']:
                return j['nombre'], j['abbrev']
    if 'doi' in bibitem:
        for j in known_journals:
            if bibitem['doi'].find(j['doi']) != -1:
                if j['issn'] != []:
                    bibitem['issn'] = j['issn'][0]
                return j['nombre'], j['abbrev']
    if 'journal' in bibitem:
        for j in known_journals:
            if re.search(j['regexp'], bibitem['journal']) is not None:
                if j['issn'] != []:
                    bibitem['issn'] = j['issn'][0]
                return j['nombre'], j['abbrev']
        abbrev = create_initials(bibitem['journal'])
        return bibitem['journal'], abbrev
    else:
        return '', ''


def mathmode(strng):
    """Agrega math mode a titulos y abstracts"""
    mathexp = re.compile('([\\^])[{]([^}\\$]+)[}]'), re.compile(
        '([_])[{]([^}$]+)[}]'), re.compile('([\\^])(\\\\[\\w]+)'), re.compile(
        '([_])(\\\\[\\w]+)'), re.compile('([\\^])([^\\{\\\\\\$])'), re.compile(
        '([_])([^\\$\\{\\\\])')
    for i in mathexp:
        strng = i.sub('$\\1{\\2}$', strng)
    return strng


def add_math(entry):
    """
  Agrega el símbolo de $ donde sea necesario (si?)
  Recibe una entrada completa (abstract, title, etc)
  """
    p = re.compile('(\\$[^\\$]+\\$)')
    L = p.split(entry)
    s = ''
    for k in L:
        if '$' not in k:
            s += mathmode(k)
        else:
            s += k
    return s


def handle_math(strng, orden=0):
    """
  Convierte entre tex <--> html.
  Si orden =0 => bib -> html
  Si orden =1 => html -> bib
  """
    mathexp = [(re.compile('\\^([^{])', re.I), '<sup>\\1</sup>'), (re.
        compile('\\^{([^{]+)}', re.I), '<sup>\\1</sup>'), (re.compile(
        '_([^{]+){', re.I), '<sub>\\1</sub>'), (re.compile('_{([^{]+)}', re
        .I), '<sub>\\1</sub>'), (re.compile('\\\\mathrm{([^{]+)}', re.I),
        '{\\1}')], [(re.compile('<sub>([^<]*)</sub>', re.I), '$_{\\1}$'), (
        re.compile('<sup>([^<]*)</sup>', re.I), '$^{\\1}$')]
    mathmarker = '', ''
    if orden == 0:
        p = re.compile('\\$([^\\$]+)\\$')
        if p.search(strng):
            ini = 0
            linecontent = ''
            iterator = p.finditer(strng)
            for match in iterator:
                strngmath = match.group()[1:-1]
                linecontent += strng[ini:match.start()]
                for i, o in mathexp[orden]:
                    strngmath = re.sub(i, o, strngmath)
                linecontent += mathmarker[0] + strngmath + mathmarker[1]
                ini = match.end()
            linecontent += strng[ini:]
        else:
            return strng
    else:
        for i, o in mathexp[orden]:
            strng = i.sub(o, strng)
        linecontent = strng
    return linecontent


rembraces_rex = re.compile('[{}]')


def removebraces(str):
    return rembraces_rex.sub('', str)


capitalize_rex = re.compile('({\\w*})')
cap_rex = re.compile('{([a-zA-Z]*)}')


def capitalizestring(data):
    """Capitalizes a string preserving character that are between braces

  Parameters
  ----------
  data = title string

  Returns
  -------
  string:
    the capitalized string (first letter is capitalized), rest are capitalized
    only if capitalized inside braces
  """
    ss_list = capitalize_rex.split(data)
    ss = ''
    count = 0
    for phrase in ss_list:
        check = phrase.lstrip()
        if check.find('{') == 0:
            ss += cap_rex.sub('\\1', phrase)
        else:
            if count == 0:
                ss += phrase.capitalize()
            else:
                ss += phrase.lower()
            count += 1
    return ss


def match_pair(expr, pair=('{', '}'), start=0):
    """
  Find the outermost pair enclosing a given expresion

  pair is a 2-tuple containing (begin, end) where both may be characters or strings
  for instance:
    pair= ('[',']')  or
    pair= ('if','end if') or
    pair= ('<div>','</div>') or ...

    """
    beg = pair[0]
    fin = pair[1]
    sstart = expr.find(beg, start)
    count = 0
    if beg == fin:
        eend = expr.find(fin, sstart + 1)
        return sstart, eend
    p = re.compile('(' + beg + '|' + fin + ')', re.M)
    ps = re.compile(beg, re.M)
    iterator = p.finditer(expr, start)
    for match in iterator:
        if ps.match(match.group()):
            count += 1
        else:
            count += -1
        if count == 0:
            return sstart, match.end()
    return None


def no_outer_parens(filecontents):
    paren_split = re.split('([(){}])', filecontents)
    open_paren_count = 0
    open_type = False
    look_next = False
    filecontents = ''
    at_rex = re.compile('@\\w*')
    for phrase in paren_split:
        if look_next:
            if phrase == '(':
                phrase = '{'
                open_paren_count += 1
            else:
                open_type = False
            look_next = False
        if phrase == '(':
            open_paren_count += 1
        elif phrase == ')':
            open_paren_count -= 1
            if open_type and open_paren_count == 0:
                phrase = '}'
                open_type = False
        elif at_rex.search(phrase):
            open_type = True
            look_next = True
        filecontents = filecontents + phrase
    return filecontents


def replace_tags(strng, what='All'):
    ww = what.lower()
    if ww == 'all' or ww == 'xml':
        for i, o in xml_tags:
            strng = strng.replace(i, o)
    if ww == 'all' or ww == 'accents':
        for i, o in accent_tags:
            strng = strng.replace(i, o)
    if ww == 'all' or ww == 'other':
        for i, o in other_tags:
            strng = re.sub(i, o, strng)
    return strng

import re
import string
import sys
import collections
from pathlib import Path
#        ################################ Variables  #####################
bibtexfields = ['abstract', 'address', 'affiliation', 'annote', 'author', 'booktitle', 'chapter',
                'crossref', 'day', 'doi', 'edition', 'editor', 'file', 'howpublished',
                'institution', 'isbn', 'issn', 'journal', 'keywords', 'month', 'note',
                'number', 'organization', 'pages', 'publisher', 'school', 'series', 'title',
                'url', 'volume', 'year']

# Fields that are output literally in bibtex form
textualfields = ['_code', 'abstract', 'address', 'annote', 'booktitle', 'chapter', 'crossref', 'doi', 'edition',
                 'file', 'howpublished', 'institution', 'isbn', 'issn', 'journal', 'keywords', 'month',
                 'note', 'number', 'organization', 'publisher', 'school', 'series', 'title', 'url', 'volume', 'year']

# Some fields that MAY appear + "_code" and "_type"
otherfields = ['_code', '_type', 'code', 'date-added', 'date-modified',
               'daynote', 'firstpage', 'journal_abbrev', 'lastpage']

# List of all possible fields.
allfields = sorted(bibtexfields + otherfields)

# Fields that MUST be present
minimalfields = ('_type', 'author')

namefields = ['author', 'editor']  # Fields that must be treated as names

# Fields that should not be wrapped (must go complete in the same line)
nowrapfields = ['url', 'doi', 'isbn', 'issn', 'crossref']

# list of all reference types. CURRENTLY NOT USED
alltypes = ('article', 'book', 'booklet', 'conference', 'inbook', 'incollection', 'inproceedings', 'manual',
            'mastersthesis', 'misc', 'phdthesis', 'proceedings', 'techreport', 'unpublished')


strings_month = [
    ('jan', 'January'),
    ('feb', 'February'),
    ('mar', 'March'),
    ('apr', 'April'),
    ('may', 'May'),
    ('jun', 'June'),
    ('jul', 'July'),
    ('aug', 'August'),
    ('sep', 'September'),
    ('oct', 'October'),
    ('nov', 'November'),
    ('dec', 'December'),
]

standard_abbrev = strings_month[:]

xml_tags = [  # xml/html entities
    ('&', '&amp;'),
    ('<', '&lt;'),
    ('>', '&gt;'),
]
# Accent tags are not used. They are handled via latex codec
accent_tags = [  # Accents and other latin symbols
    (r'\"a', "&#228;"),
    (r"\`a", "&#224;"),
    (r"\'a", "&#225;"),
    (r"\~a", "&#227;"),
    (r"\^a", "&#226;"),
    (r'\"e', "&#235;"),
    (r"\`e", "&#232;"),
    (r"\'e", "&#233;"),
    (r"\^e", "&#234;"),
    (r'\"\i', "&#239;"),
    (r"\`\i", "&#236;"),
    (r"\'\i", "&#237;"),
    (r"\^\i", "&#238;"),
    (r'\"i', "&#239;"),
    (r"\`i", "&#236;"),
    (r"\'i", "&#237;"),
    (r"\^i", "&#238;"),
    (r'\"o', "&#246;"),
    (r"\`o", "&#242;"),
    (r"\'o", "&#243;"),
    (r"\~o", "&#245;"),
    (r"\^o", "&#244;"),
    (r'\"u', "&#252;"),
    (r"\`", "&#249;"),
    (r"\'", "&#250;"),
    (r"\^", "&#251;"),
    (r'\"A', "&#196;"),
    (r"\`A", "&#192;"),
    (r"\'A", "&#193;"),
    (r"\~A", "&#195;"),
    (r"\^A", "&#194;"),
    (r'\"E', "&#203;"),
    (r"\`E", "&#200;"),
    (r"\'E", "&#201;"),
    (r"\^E", "&#202;"),
    (r'\"I', "&#207;"),
    (r"\`I", "&#204;"),
    (r"\'I", "&#205;"),
    (r"\^I", "&#206;"),
    (r'\"O', "&#214;"),
    (r"\`O", "&#210;"),
    (r"\'O", "&#211;"),
    (r"\~O", "&#213;"),
    (r"\^O", "&#212;"),
    (r'\"U', "&#220;"),
    (r"\`", "&#217;"),
    (r"\'", "&#218;"),
    (r"\^", "&#219;"),
    (r"\~n", "&#241;"),
    (r"\~N", "&#209;"),
    (r"\c c", "&#231;"), (r"\c C", "&#199;"), (r"\circ", "o")
]

other_tags = [
    ('([^\\\\])~', r'\g<1> '),  # Remove tilde (used in LaTeX for spaces)
    # Remove other unknown commands
    (r'\\textbf{([^{]+)}', r'<b>\g<1></b>'), (r'\\\\emph{([^{]+)}',
                                              r'<em>\g<1></em>'), (r'\\textit{([^{]+)}', r'<i>\1</i>'), (r'\[^ {]+{(.+)}', r'\g<1> ')
]

# Some journal data.
# DOI is useful when a journal uses a common (unique) root
# ISSN is unique but ads does not provides it. We list (when available) two issn: [print, online]
# Regular expression will give us a general (but not exact) matching strategy
journal_data = [
    {
        'regexp': r'Nuc\w*[\.]?\s*Inst\w*[\.]?\s*(and)?\s*Meth\w*[\.]?\s*(in)?\s*(Phys(ics)?[\.]?)?\s*(R(esearch)?[\.]?)?\s*(S(ection)?[\.]?)?\s*B|nimb',
        'nombre': r'Nuclear Instruments and Methods in Physics Research B',
        'abbrev': 'NIMB',
        'doi': '10.1016/j.nimb',
        'issn': ['0168-583X']},
    {
        'regexp': r'Int(ernational)?[\.]?\s*J(our(nal)?)?[\.]?\s*(of)?\s*Q(uantum)?[\.]?\s*Ch(em(istry)?)?[\.]?',
        'nombre': 'International Journal of Quantum Chemistry',
        'abbrev': 'IJQC',
        'doi': '10.1002/qua',
        'issn': [
            '1097-461X',
            '0020-7608']},
    {
        'regexp': r'J(ournal)?[\.]?\s*(of)?\s*Phys(ics)?[\.]?\s*A(\s*:\s*Math(ematical)?\s*(and)?\s*(General)?)?|jpa',
        'nombre': r'Journal of Physics A: Mathematical and General',
        'abbrev': 'JPA',
        'doi': '10.1088/0305-4470',
        'issn': [
            '1751-8113',
            '1751-8121']},
    {'regexp': r'J(ournal)?[\.]?\s*(of)?\s*Phys(ics)?[\.]?\s*B(\s*:\s*At(omic)?[\.]?\s*Mol(ecular)?[\.]?\s*(and)?\s*(Opt(ical)?)?\s*Phys(ics)?)?|jpb',
        'nombre': r'Journal of Physics B: Atomic Molecular and Optical Physics', 'abbrev': 'JPB', 'doi': '10.1088/0953-4075', 'issn': ['0953-4075', '1361-6455']},
    {'regexp': r'Ph\w*[\.]? Rev\w* A|pra', 'nombre': r'Physical Review A',
        'abbrev': 'PRA', 'doi': '10.1103/PhysRevA', 'issn': ['1050-2947', '1094-1622']},
    {'regexp': r'Ph\w*[\.]? Rew* B|prb', 'nombre': r'Physical Review B',
        'abbrev': 'PRB', 'doi': '10.1103/PhysRevB', 'issn': ['1098-0121', '1550-235x']},
    {'regexp': r'Ph\w*[\.]? Rew* C|prc', 'nombre': r'Physical Review C',
        'abbrev': 'PRC', 'doi': '10.1103/PhysRevC', 'issn': ['0556-2813', '1089-490X']},
    {'regexp': r'Ph\w*[\.]? Rev\w*[\.]? L\w*[\.]?|prl', 'nombre': r'Physical Review Letters',
        'abbrev': 'PRL', 'doi': '10.1103/PhysRevLett', 'issn': ['0031-9007', '1079-7114']},
    {'regexp': r'Rev\w*[\.]?\s*(of)?\s*Mod(ern)?\s*Phys(ics)?|rmp',
        'nombre': r'Review of Modern Physics',
        'abbrev': 'RMP',
        'doi': 'Not Available',
        'issn': ['0034-6861',
                 '1539-0756']},
    #
    {'regexp': r'J\w*[\.]? Phy\w*[\.]? Conf\w*[\.]?|jcps',
        'nombre': r"Journal of Physics Conference Series",
        'abbrev': 'JPCS',
        'doi': '10.1088/1742-6596',
        'issn': ['1742-6588 ',
                 '1742-6596']},
    {'regexp': r'Comp\w*[\.]? Phys\w*[\.]? C\w*[\.]?|cpc',
        'nombre': r"Computer Physics Communications",
        'abbrev': 'CPC',
        'doi': '10.1016/j.cpc',
        'issn': ['0010-4655']},
    {'regexp': r'Phys\w*[\.]? Scrip\w*[\.]?',
        'nombre': r"Physica Scripta Volume T",
        'abbrev': 'PS',
        'doi': '10.1238/Physica.Topical',
        'issn': []},
    {'regexp': r'Rad\w*[\.]? Phy\w*[\.]? Chem\w*[\.]?|rpc',
        'nombre': r"Radiation Physics and Chemistry",
        'abbrev': 'RPC',
        'doi': '10.1016/j.radphyschem',
        'issn': []},
    {'regexp': r'F\w*[\.]? Bo\w*[\.]? Sys\w*[\.]?|fbs',
        'nombre': r"Few-Body Systems",
        'abbrev': 'FBS',
        'doi': '10.1007/s00601',
        'issn': []},
    {'regexp': r'Eur\w*[\.]? J\w*[\.]? Phys\w*[\.]?|ejp',
        'nombre': r"European Journal of Physics",
        'abbrev': 'EJP',
        'doi': 'Not Available',
        'issn': ['0143-0807',
                 '1361-6404']},
    {'regexp': r'Phys\w*[\.]? Let\w*[\.]? A|pla', 'nombre': r"Physics Letters A",
        'abbrev': 'PLA', 'doi': 'Not Available', 'issn': ['0375-9601']},
    #
    {'regexp': r'Am\w*[\.]? J\w*[\.]? P|ajp',
        'nombre': r"American Journal of Physics",
        'abbrev': 'AJP',
        'doi': '10.1119/',
        'issn': ['0002-9505']},
    {'regexp': r'J\w*[\.]? Chem\w*[\.]? Phys\w*[\.]?|jcp|JCP',
        'nombre': r"Journal of Chemical Physics",
        'abbrev': 'JPC',
        'doi': 'Not Available',
        'issn': ['0021-9606',
                 '1089-7690']}
]


def make_unique(lista):
  """Keep only unique elements of list
  """
  f = []
  for it in lista:
    if it not in f:
      f.append(it)
  return f


def is_string_like(obj):
  'Return True if *obj* looks like a string (from matplotlib)'
  if isinstance(obj, str):
    return True
  try:
    obj + ''
  except BaseException:
    return False
  return True


def isIterable(obj):
  try:
    iter(obj)
  except BaseException:
    return False
  return True


def is_writable_file_like(obj):
  '''return true if *obj* looks like a file object with a *write* method (from matplotlib)'''
  return hasattr(obj, 'write') and isinstance(obj.write, collections.Callable)


def is_readable_file_like(obj):  # No se si funciona
  '''return true if *obj* looks like a file object with a *read* method '''
  return hasattr(obj, 'read') and isinstance(obj.read, collections.Callable)


def to_filehandle(fname, flag='r', return_opened=False, encoding=None):
  """ from matplotlib
  *fname* can be a filename or a file handle.  Support for gzipped
  files is automatic, if the filename ends in .gz.  *flag* is a
  read/write flag for :func:`file`
  """
  if is_string_like(fname):
    fh = Path(fname).open(mode=flag)
    opened = True
  elif isinstance(fname, Path):
    fh = fname.open(mode=flag)
  elif hasattr(fname, 'seek'):
    fh = fname
    opened = False
  else:
    raise ValueError('fname must be a pathlib Path, string or file handle')
  if return_opened:
    return fh, opened
  return fh


def tsplit(s, sep):
  """Splits a string like split but allows for several char delimiters
  Example:
  >>> tsplit("hello,my|name+is-bob", (",", "|", "+", "-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", ",|+-")
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", list(",|+-"))
  ['hello', 'my', 'name', 'is', 'bob']
  >>> tsplit("hello,my|name+is-bob", "+")
  ['hello,my|name', 'is-bob']

  Arguments:
  - `s`: string to separate
  - `sep`: separators
  """
  stack = [s]
  for char in sep:
    pieces = []
    for substr in stack:
      pieces.extend(substr.split(char))
    stack = pieces
  return stack


def openfile(fname=None, intent='r', encoding=None):
  if fname is None or fname == '-' or fname == '':
    if intent == 'r':
      fi = sys.stdin
    else:
      fi = sys.stdout
    return fi

  return to_filehandle(fname=fname, flag=intent, encoding=encoding)


reg_simplify = re.compile(r'\W')


def oversimplify(strng):
  """
  (Over)simplify a string by converting to latex and then removing everything that does not belong to a word
  """
  s = strng.encode('latex').decode('utf-8')
  s = reg_simplify.sub('', s)
  return s


def closefile(fi):
  if fi != sys.stdin and fi != sys.stdout and fi != sys.stderr:
    fi.close()


reg_defstrng = re.compile(
    '[{]*DEFINITIONOFSTRING[(](\\w+)[)][{]*(\\s*[#]*\\s*["]*\\s*\\w*\\s*["]*\\s*)[}]*', re.I)
# reg_defstrng= re.compile('[{]*DEFINITIONOFSTRING[(](\w+)[)][}]*',re.I)


def verify_entry(b):
  """Verify that the entry is valid (minimal fields are present)

  Parameters
  ----------
  b : dict
    entry values

  Returns
  -------
  bool:
    True if is valid
  """
  for f in minimalfields:
    if f not in b:
      return False
  return True


def add_journal_abbrev(b):
  """Try to add journal abbreviation

  Parameters
  ----------
  b : dict
    entry values
  """
  if 'journal' in b:
    if 'journal_abbrev' not in b:
      # Create one abbrev
      journal, abbrev = identify_some_journals(b)
      b['journal_abbrev'] = abbrev
      b['journal'] = journal


def replace_abbrevs(strs, sourcestrng):
  if 'DEFINITIONOFSTRING' in sourcestrng or 'definitionofstring' in sourcestrng:
    v = sourcestrng
    for abbrev, defin in list(strs.items()):
      v = v.replace('DEFINITIONOFSTRING(%s)' % (abbrev), defin)
      v = v.replace('DEFINITIONOFSTRING(%s)' % (abbrev).lower(), defin)
      v = v.replace('DEFINITIONOFSTRING(%s)' % (abbrev).upper(), defin)
      v = v.replace('definitionofstring(%s)' % (abbrev), defin)
      v = v.replace('definitionofstring(%s)' % (abbrev).lower(), defin)
      v = v.replace('definitionofstring(%s)' % (abbrev).upper(), defin)
      d = ' '.join([x.strip().strip('"') for x in v.split('#')])
    return d
  return sourcestrng


def process_name(name):
  """
  Process one name and separate it in '[von, Last, First, Jr]'
  """
  def getnames_form3(a):
    """
    Case with two commas: the name is of the format
    von Last, Jr, First
    like in: von Hicks, III, Michael
    """
    full_last = a[0].strip()
    full_first = a[2].strip()
    junior = a[1].strip()
    von, last = get_vonlast(full_last)
    return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

  def getnames_form2(a):
    """
    Case with one comma: the name is of the format
    von Last, First
    like in: von Hicks, Michael
    """
    full_last = a[0].strip()
    full_first = a[1].strip()
    junior = ''
    von, last = get_vonlast(full_last)
    return [von.strip(), last.strip(), full_first.strip(), junior]

  def getnames_form1(a):
    """
    Case with NO commas: the name is of the format
    First von Last
    like in: Michael von Hicks
    """
    last = a[0].split(' ')
    nfn = 0
    for l in last:
      if l != "" and not l[0].islower():
        nfn += 1
      else:
        break
    if nfn == len(last):
      nfn = -1

    full_first = ' '.join(last[:nfn])
    full_first = full_first.replace('.', ' ')
    full_last = ' '.join(last[nfn:])
    junior = " "
    von, last = get_vonlast(full_last)
    return [von.strip(), last.strip(), full_first.strip(), junior.strip()]

  def get_vonlast(full_last):
    von = ""
    last = ""

    for l in full_last.split(' '):
      if len(l) > 0 and l[0].islower():
        von += l.lower() + " "
      else:
        last += l + " "
    return von, last

  # Start the processing
  a = name.split(',')
  if len(a) == 3:
    fullname = getnames_form3(a)
  elif len(a) == 2:
    fullname = getnames_form2(a)
  elif len(a) == 1:
    fullname = getnames_form1(a)
  else:
    fullname = []

  return fullname


def create_initials(strng):
  return ''.join([x for x in [x[0] for x in strng.split()] if x.isupper()])


def identify_some_journals(bibitem, known_journals=journal_data):

  # First try to identify the ISSN
  if 'issn' in bibitem:
    for j in known_journals:
      if bibitem['issn'] == j['issn']:
        return j['nombre'], j['abbrev']

  # Then the DOI
  if 'doi' in bibitem:
    for j in known_journals:
      if bibitem['doi'].find(j['doi']) != -1:
        if j['issn'] != []:
          bibitem['issn'] = j['issn'][0]
        return j['nombre'], j['abbrev']

  # If DOI does not work, identify the journal with the regexp
  if 'journal' in bibitem:
    for j in known_journals:
      if re.search(j['regexp'], bibitem['journal']) is not None:
        if j['issn'] != []:
          bibitem['issn'] = j['issn'][0]
        return j['nombre'], j['abbrev']

    # If it is not a know journal, get the abbreviation from the first letters
    # of each word
    abbrev = create_initials(bibitem['journal'])
    return bibitem['journal'], abbrev
  else:
    # If nothing worked return emtpy strings
    return '', ''


def mathmode(strng):
  """Agrega math mode a titulos y abstracts"""
  mathexp = (
      # forma: a^{b}
      re.compile(r"([\^])[{]([^}\$]+)[}]"), re.compile(r"([_])[{]([^}$]+)[}]"),
      # forma: a^\beta
      re.compile(r"([\^])(\\[\w]+)"), re.compile(r"([_])(\\[\w]+)"),
      # forma: a^b
      re.compile(r"([\^])([^\{\\\$])"), re.compile(r"([_])([^\$\{\\])")
  )
  for i in mathexp:
    strng = i.sub(r"$\1{\2}$", strng)
  return strng


def add_math(entry):
  """
  Agrega el símbolo de $ donde sea necesario (si?)
  Recibe una entrada completa (abstract, title, etc)
  """
  p = re.compile(r'(\$[^\$]+\$)')  # Find math substrngings
  L = p.split(entry)
  s = ""
  for k in L:
    if "$" not in k:
      s += mathmode(k)
    else:
      s += k
  return s


def handle_math(strng, orden=0):
  """
  Convierte entre tex <--> html.
  Si orden =0 => bib -> html
  Si orden =1 => html -> bib
  """
  mathexp = ([(re.compile(r'\^([^{])', re.I), r'<sup>\1</sup>'), (re.compile(r'\^{([^{]+)}', re.I), r'<sup>\1</sup>'), (re.compile(r'_([^{]+){', re.I), r'<sub>\1</sub>'), (re.compile(r'_{([^{]+)}', re.I), r'<sub>\1</sub>'), (re.compile(r'\\mathrm{([^{]+)}', re.I), r'{\1}')
              ],
             [(re.compile(r'<sub>([^<]*)</sub>', re.I), r'$_{\1}$'), (re.compile(r'<sup>([^<]*)</sup>', re.I), r'$^{\1}$')
              ])
#   mathmarker= ('<math>','</math>')
  mathmarker = ('', '')

  if orden == 0:
    p = re.compile(r'\$([^\$]+)\$')  # Find math substrngings
    if p.search(strng):
      ini = 0
      linecontent = ''
      iterator = p.finditer(strng)
      for match in iterator:
        strngmath = match.group()[1:-1]
        linecontent += strng[ini:match.start()]
        for i, o in mathexp[orden]:
          strngmath = re.sub(i, o, strngmath)
        linecontent += mathmarker[0] + strngmath + mathmarker[1]
        ini = match.end()
      linecontent += strng[ini:]
    else:
      return strng
  else:
    for i, o in mathexp[orden]:
      strng = i.sub(o, strng)
    linecontent = strng

  return linecontent


# return the string parameter without braces
#
rembraces_rex = re.compile('[{}]')


def removebraces(str):
  return rembraces_rex.sub('', str)


capitalize_rex = re.compile(r'({\w*})')
cap_rex = re.compile('{([a-zA-Z]*)}')


def capitalizestring(data):
  """Capitalizes a string preserving character that are between braces

  Parameters
  ----------
  data = title string

  Returns
  -------
  string:
    the capitalized string (first letter is capitalized), rest are capitalized
    only if capitalized inside braces
  """
  ss_list = capitalize_rex.split(data)
  ss = ''
  count = 0
  for phrase in ss_list:
    check = phrase.lstrip()

    # keep phrase's capitalization the same
    if check.find('{') == 0:
      ss += cap_rex.sub(r'\1', phrase)
    else:
      # first word --> capitalize first letter (after spaces)
      if count == 0:
        ss += phrase.capitalize()
      else:
        ss += phrase.lower()
      count += 1
  return ss


def match_pair(expr, pair=(r'{', r'}'), start=0):
  """
  Find the outermost pair enclosing a given expresion

  pair is a 2-tuple containing (begin, end) where both may be characters or strings
  for instance:
    pair= ('[',']')  or
    pair= ('if','end if') or
    pair= ('<div>','</div>') or ...

    """

  beg = pair[0]
  fin = pair[1]

  #   find first opening
  sstart = expr.find(beg, start)

  count = 0

  if beg == fin:
    eend = expr.find(fin, sstart + 1)
    return sstart, eend

  p = re.compile('(' + beg + '|' + fin + ')', re.M)
  ps = re.compile(beg, re.M)

  iterator = p.finditer(expr, start)

  for match in iterator:
    if ps.match(match.group()):
      count += 1
    else:
      count += -1

    if count == 0:
      return sstart, match.end()

  return None


def no_outer_parens(filecontents):
  # JF: TODO We should check/change this routine Convierte los
  # parentesis a llaves cuando contienen el item completo.
  # Do checking for open parens will convert to braces
  paren_split = re.split('([(){}])', filecontents)

  open_paren_count = 0
  open_type = False
  look_next = False

  # rebuild filecontents
  filecontents = ''

  at_rex = re.compile(r'@\w*')

  for phrase in paren_split:
    if look_next:
      if phrase == '(':
        phrase = '{'
        open_paren_count += 1
      else:
        open_type = False
      look_next = False

    if phrase == '(':
      open_paren_count += 1
    elif phrase == ')':
      open_paren_count -= 1
      if open_type and open_paren_count == 0:
        phrase = '}'
        open_type = False
    elif at_rex.search(phrase):
      open_type = True
      look_next = True

    filecontents = filecontents + phrase
  return filecontents


def replace_tags(strng, what='All'):
  ww = what.lower()
  if ww == 'all' or ww == 'xml':
    # encode character entities
    for i, o in xml_tags:
      strng = strng.replace(i, o)

  if ww == 'all' or ww == 'accents':
    # latex-specific character replacements
    for i, o in accent_tags:
      # strng = string.replace(strng, i, o)
      strng = strng.replace(i, o)

  if ww == 'all' or ww == 'other':
    # Other LaTeX tags, handled by regexps
    for i, o in other_tags:
      strng = re.sub(i, o, strng)

  return strng

# def filter_author(biblist, filter_options):
#   delete=[]
#   for ident,item in biblist.iteritems():
#     valid = True
#     if item.has_key('author'):
#       aut = [ ' '.join(k) for k in item['author']]
#     else:
#       print ident, item
#       sys.exit(1)


# def filter_bib(biblist, filter_options):
#   delete=[]
#   for ident,item in biblist.iteritems():
#     valid = True
#     for k,v,cond in filter_options :
#       valid= item.has_key(k) and valid
#       if valid:
#         if type(item[k]) == list:
#           if k == 'author':
#             aut = [ ' '.join( s ) for s in item['author']]
#             data= ' '.join( aut )
#           else:
#             data= join( item[k] )
#         else:
#           data= item[k]
#         valid = valid and ((v in data.lower()) == cond)

#         if not valid:
#           delete.append(ident)
#           break
#       else:
#         delete.append(ident)
#         break

#   # Collect all matching and return them
#   bib={}
#   for ident in biblist.keys():
#     if ident not in delete:
#       bib[ident]= biblist[ident].copy()

#   return bib
