"""
usitools.py module

Ein Screenscraping-Parser fuer die USI-Kursliste (http://www.usi.at) mit aktuellen Teilnehmerzahlen.
Nur kurz getestet, Bugs wahrscheinlich. Python ab Version 2.1 (2.2 empfohlen).
Stand Wintersemester 2004

Klaus A. Brunner, 2004-09
"""

import sgmllib
import urllib2

# -----------------------------------------------------------------

class NotModifiedHandler(urllib2.BaseHandler):
     """
     handles if-modified-since 304 responses. copied from
     http://www.hole.fi/jajvirta/weblog/20030928T2101.html (Jarno Virtanen)
     """
     def http_error_304(self, req, fp, code, message, headers):
         addinfourl = urllib2.addinfourl(fp, headers, req.get_full_url())
         addinfourl.code = code
         return addinfourl

class UsiParser(sgmllib.SGMLParser):
    """
    Ein Screenscraping-Parser fuer die USI-Kursliste
    (http://www.usi.at) mit aktuellen Teilnehmerzahlen.
    """

    def parseUrl(self, url):
        """
        aus einer url parsen, dabei wird versucht mit last-modified-header
        zu arbeiten, um nicht immer wieder die mehreren hundert kb runterzuladen
        return: true, wenn neue daten verarbeitet worden sind. sonst false.
        """
        if self.url != url:
            self.url = url
            self.last_modified_header = ''
            
        req = urllib2.Request(self.url)
        opener = urllib2.build_opener(NotModifiedHandler())

        if self.last_modified_header:
            req.add_header("If-Modified-Since", self.last_modified_header)
        
        url_handle = opener.open(req)

        if hasattr(url_handle, 'code') and url_handle.code == 304:
            if self.verbose: print "the web page has not been modified"
            return 0
        else:
            headers = url_handle.info()
            self.last_modified_header = headers.getheader("Last-Modified")
        
            # daten runterladen
            text = url_handle.read()
            url_handle.close()

            # in den parser schicken
            self.parse(text)
            return 1

    def parse(self, s):
        self.feed(s)
        self.close()

    def __init__(self, verbose=0):
        sgmllib.SGMLParser.__init__(self, verbose)
        self.kurse = {}
        self.aktuelle_zeile = {}
        self.in_zeile = 0
        self.in_wert = 0
        self.wert_zaehler = 0
        self.aktuelle_id = 0
        self.leerfeld = 1
        self.last_modified_header = ''
        self.verbose = verbose
        self.url = ''
        self.aktuelle_sparte = ''
        self.in_spartentitel = 0

        # reihenfolge der felder, integer-flag
        self.positionen = {1:('tag', 0), 2:('termin', 0), 3:('dauer', 0),
                           4:('ort', 0), 5:('raum', 0), 6:('limit', 1),
                           7:('teilnehmer', 1), 8:('instruktor', 0)}

    def start_a(self, attributes):
        for name, value in attributes:
            if name == "class" and int(value) == 2:
                self.in_spartentitel = 1
                print "hallo"

    def end_a(self):
        if self.in_spartentitel:
            self.in_spartentitel = 0

    def start_tr(self, attributes):
        self.in_zeile = 1
        self.aktuelle_zeile = {}
        self.wert_zaehler = 0

    def end_tr(self):
        self.in_zeile = 0
        if self.aktuelle_zeile.has_key('instruktor'):
            self.aktuelle_zeile['sparte'] = self.aktuelle_sparte
            self.kurse[self.aktuelle_id] = self.aktuelle_zeile

    def start_td(self, attributes):
        for name, value in attributes:
            # die eigentlichen daten sind immer in td-elementen der klassen 1-4
            if name == "class" and int(value) >= 1 and int(value) <= 4:
                self.in_wert = 1
                self.leerfeld = 1
                
    def end_td(self):
        # leere td-elemente kommen nie ins handle_data, in der liste
        # entspricht das normalerweise der zahl 0 (angemeldete teilnehmer)
        if self.leerfeld and self.in_wert and self.wert_zaehler > 0:
            self.aktuelle_zeile[ self.positionen[self.wert_zaehler][0] ] = 0
        
        self.in_wert = 0
        self.wert_zaehler += 1

    def handle_data(self, data):
        data = unicode(data.strip(), 'latin-1')	# zeichensatz ist nicht explizit angegeben -> default ISO 8859-1
        if self.in_wert:
            if self.wert_zaehler > 0:
                self.leerfeld = 0
                # wenn's ein integer ist, gleich umwandeln
                if self.positionen[self.wert_zaehler][1]:
                    if data:
                        data = int(data)
                    else:
                        data = 0
                   
                self.aktuelle_zeile[ self.positionen[self.wert_zaehler][0] ] = data
            else: # ist es die kursnummer?
                self.aktuelle_id = int(data)
        elif self.in_spartentitel:
            self.aktuelle_sparte = data
                
# -----------------------------------------------------------------

if __name__ == "__main__":
    import cPickle

    kursliste_url = 'http://www.univie.ac.at/USI-Wien/ueb/kursliste.htm'
    parser_cache  = 'usiparser.pickle'

    # falls es schon ein objekt gibt, das wiederherstellen
    try:
        pickle_file = open(parser_cache, 'r')
        parser = cPickle.load(pickle_file)
        pickle_file.close()
    except:
        parser = UsiParser()

    # runterladen und parsen (falls noetig)
    parser.parseUrl(kursliste_url)

    # ein paar tests
    kursnr = 1313
    print parser.kurse[kursnr]
    print "Freie Plaetze: %d" % (parser.kurse[kursnr]['limit'] - parser.kurse[kursnr]['teilnehmer'])

    print "Anzahl der Kurse: %d" % (len(parser.kurse.keys()))

    teilnehmer = 0
    for kurs in parser.kurse.values():
        teilnehmer += kurs['teilnehmer']
    print "Angemeldete Teilnehmer gesamt: %d" % (teilnehmer)


    # speichern fuer naechstes mal
    pickle_file = open(parser_cache, 'w')
    cPickle.dump(parser, pickle_file)
    pickle_file.close()
    

