import re

class Bibliography(dict):
    def __init__(self, txt, macros={}):
        """
        txt:
            a string which represents the entire bibtex entry. A typical
            entry is of the form:

                @ARTICLE{ellington:84:part3,
                  author = {Ellington, C P},
                  title = {The Aerodynamics of Hovering Insect Flight. III. Kinematics},
                  journal = {Philosophical Transactions of the Royal Society of London. Series B, Biological Sciences},
                  year = {1984},
                  volume = {305},
                  pages = {41-78},
                  number = {1122},
                  owner = {Srinath},
                  pdf = {C:\srinath\research\papers\Ellington-3-Kinematics.pdf},
                  timestamp = {2006.01.02},
                }
        """
        
        if macros:
            for k, v in macros.iteritems():
                txt = txt.replace(k, '{'+v+'}')
        
        m = re.match(r'\s*@(\w+){((\S+),)?(.*)}\s*', txt, re.MULTILINE | re.DOTALL)
        if not m:
            return None

        self['bibtype'] = m.group(1).capitalize()
        self['key'] = m.group(3)
        self['body'] = m.group(4)

        body = self['body']
        self['bodytext'] = ''
        while 1:
            m = re.search(r'(\S+?)\s*=\s*(.)', body)
            if not m:
                break

            field = m.group(1)

            body = body[(m.start(2)+1):]
            if m.group(2) == '{':
                # search for the next closing brace. This is not simply a
                # matter of searching for the next closing brace since
                # braces can be nested. The following code basically goes
                # to the next } which has not already been closed by a
                # following {.
                mniter = re.finditer(r'{|}', body)

                count = 1
                while 1:
                    try:
                        mn = mniter.next()
                    except StopIteration:
                        return None

                    if mn.group(0) == '{':
                        count += 1
                    else:
                        count -= 1

                    if count == 0:
                        value = body[:(mn.start(0))]
                        break

            elif m.group(2) == '"':
                # search for the next unquoted double-quote. To be more
                # precise, a double quote which is preceded by an even
                # number of double quotes.
                mn = re.search(r'(?!\\)(\\\\)*"', body)
                if not mn:
                    return None

                value = body[:(mn.start(0))]

            else:
                mn = re.match(r'\d+', body)
                if not mn:
                    raise "Only completely numeral fields can be left unquoted"

                value = m.group(2) + mn.group(0)

            self[field] = re.sub(r'\s+', ' ', value)
            body = body[(mn.start(0)+1):]

            self['bodytext'] += ('  %s: %s\n' % (field, value))
            if self['bibtype'].lower() == 'string':
                self['macro'] = {field: value}

        self['bodytext'] = self['bodytext'].rstrip()
        

    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            return ''
        
    def __str__(self):
        if self['bibtype'].lower() == 'string':
            return 'String: %(macro)s' % self

        elif self['bibtype'].lower() == 'article':
            return ('Article [%(key)s]\n' +
                    'TI "%(title)s"\n' +
                    'AU %(author)s\n' +
                    'IN In %(journal)s, %(year)s') % self

        elif self['bibtype'].lower() == 'conference':
            return ('Conference [%(key)s]\n' +
                    'TI "%(title)s"\n' +
                    'AU %(author)s\n' +
                    'IN In %(booktitle)s, %(year)s') % self

        elif self['bibtype'].lower() == 'mastersthesis':
            return ('Masters [%(key)s]\n' + 
                    'TI "%(title)s"\n' + 
                    'AU %(author)s\n' + 
                    'IN In %(school)s, %(year)s') % self

        elif self['bibtype'].lower() == 'phdthesis':
            return ('PhD [%(key)s]\n' + 
                    'TI "%(title)s"\n' + 
                    'AU %(author)s\n' + 
                    'IN In %(school)s, %(year)s') % self

        elif self['bibtype'].lower() == 'book':
            return ('Book [%(key)s]\n' +
                    'TI "%(title)s"\n' + 
                    'AU %(author)s\n' + 
                    'IN %(publisher)s, %(year)s') % self

        else:
            s = '%(bibtype)s [%(key)s]\n' % self
            if self['title']:
                s += 'TI "%(title)s"\n' % self
            if self['author']:
                s += 'AU %(author)s\n' % self
            for k, v in self.iteritems():
                if k not in ['title', 'author', 'bibtype', 'key', 'id', 'file', 'body', 'bodytext']:
                    s += 'MI %s: %s\n' % (k, v)

            return s.rstrip()

    def satisfies(self, filters):
        for field, regexp in filters:
            if not re.search(regexp, self[field], re.I):
                return False

        return True

class BibFile:

    def __init__(self, filelist=''):
        self.bibentries = []
        self.filters = []
        self.macros = {}
        self.sortfields = []
        if filelist:
            for f in filelist.splitlines():
                self.addfile(f)

    def addfile(self, file):
        fields = open(file).read().split('@')
        for f in fields:
            if not (f and re.match('string', f, re.I)):
                continue

            b = Bibliography('@' + f)
            self.macros.update(b['macro'])

        for f in fields:
            if not f or re.match('string', f, re.I):
                continue

            b = Bibliography('@' + f, self.macros)
            if b:
                b['file'] = file
                b['id'] = len(self.bibentries)
                self.bibentries += [b]


    def addfilter(self, filterspec):
        self.filters += [filterspec.split()]

    def rmfilters(self):
        self.filters = []

    def __str__(self):
        s = ''
        for b in self.bibentries:
            if b['key'] and b.satisfies(self.filters):
                s += '%s\n\n' % b
        return s

    def addsortfield(self, field):
        self.sortfields += [field]

    def rmsortfields(self):
        self.sortfields = []

    def sort(self):
        def cmpfun(b1, b2):
            for f in self.sortfields:
                c = cmp(b1[f], b2[f])
                if c:
                    return c
            return 0
        self.bibentries.sort(cmp=cmpfun)

if __name__ == "__main__":
    import sys

    bf = BibFile(sys.argv[1])
    print bf
