#!/usr/bin/env python
#-*- coding: UTF-8 -*-
#
# Aufgabe 8: WikiStrukturWrapper
# Author: Matthias Rebel, 731220


import urllib2, re
from BeautifulSoup import BeautifulSoup
#import BeautifulSoup

#   Funktionen ______________________________________________________________________________________

#   getWikiPage
#   Quelltext von der Latexfolie: http://odo.dwds.de/python/s3/urllib.pdf, 13.06.08
def getWikiPage(url):
    " opens an url and returns its html-sourcecode "
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3',
        'Accept-Encoding': 'deflate',
        'Accept-Charset': 'utf-8',
        'Referer': 'http://de.wikipedia.org/wiki/Hauptseite',
        }
    try:
        request = urllib2.Request(url, None, headers)
        response = urllib2.urlopen(request)
        content = response.read()
        response.close()
        return content
    except urllib2.HTTPError:
        print "HTTPError: %s" % url


#   get X ----------------------------------------------------------------------------------

def getSubcats(SoupString, catIndex):
    """ schreibt die Ergebnisse aus dem Quelltextbereich in eine Liste: für Unterkategorien """
    muster = re.compile(r'Category" href="(.*?)">(.*?)</a> <span title="(.*?)">', re.DOTALL)
    categories = muster.findall(SoupString)

    subcatListe = []; subCatIndex = 1
    for (addUrl,nameTochter,info) in categories:
        no = str(catIndex)+'_'+str(subCatIndex)
        subcatListe.append((no, nameTochter, addUrl, info))
        subCatIndex += 1
    return subcatListe

def getPages(SoupString):
    """ schreibt die Ergebnisse aus dem Quelltextbereich in eine Liste: für Seiten """
    muster = re.compile(r'href="(.*?)" title="(.*?)">', re.DOTALL)
    pages = muster.findall(SoupString)

    pageList = []
    for (addUrl,nameBlatt) in pages:
        pageList.append((addUrl,nameBlatt))
    return pageList

def getFiles(SoupString):
    """ schreibt die Ergebnisse aus dem Quelltextbereich in eine Liste: für Dateien """
    muster = re.compile(r'class="image" title="(.*?)"', re.DOTALL)
    files = muster.findall(SoupString)

    fileList = []
    for nameBlatt in files:
        fileList.append(nameBlatt)
    return fileList

#   prettyprint X ---------------------------------------------------------------------

def prettyfySubcatListe(subcats):
    """ wandelt die Ergebnisliste in einen String für die AusgabeDatei """
    sexy = ''; countDown = len(subcats)
    for (a, b, c, d) in subcats:
        if countDown != 1:
            sexy = sexy+(a+' '+b+', ')
            countDown -= 1
        else:
            sexy = sexy+(a+' '+b)
    return len(subcats), sexy

def prettyfyPages(pages):
    """ wandelt die Ergebnisliste in einen String für die AusgabeDatei """
    sexy = ''; countDown = len(pages)
    for (a, b) in pages:
        if countDown != 1:
            sexy = sexy+(b+', ')
            countDown -= 1
        else:
            sexy = sexy+(b)
    return len(pages), sexy

def prettyfyFiles(files):
    """ wandelt die Ergebnisliste in einen String für die AusgabeDatei """
    sexy = ''; countDown = len(files)
    for a in files:
        if countDown != 1:
            sexy = sexy+(a+', ')
            countDown -= 1
        else:
            sexy = sexy+(a)
    return len(files), sexy


#   informationsanalyse: X enthält 23 Unterkategorien, 8 Seiten und 1555 Dateien  
def parse_info_for_next_step(info):
    """ die aktulle category 'enthält X Unterkategorie(n), Y Seite(n) und Z Datei(en) 
        r'enthält (.*?) Unterkat\w+, (.*?) Seit\w+ und (.*?) Dat\w+' """
    muster = re.compile(r'enthält (.*?) Unterkat\w+, (.*?) Seit\w+ und (.*?) Dat\w+', re.DOTALL)
    nextStep = muster.findall(info)
    return tuple(nextStep)


##############################################################################################
###   wrapper   ##############################################################################
##############################################################################################


def wrapper(catIndex,category,url,info):
    """ Der wrapper(catIndex,category,url,info) :)  
        Eine Kategorie besteht aus Unterkategorien, Seiten und/oder Dateien. 
        Unterkategorien sind Töchter; Seiten & Dateien sind Terminale
        
        Im Wert info stehen die aus dem Quelltext extrahierten Informationen zu jeder Kategorie. 
        Dieser String wird in nextStep analysiert und sagt dem wrapper ob er nach dateien, seiten 
        und/oder unterkategorien suchen soll. 
        Der bereits erwähnt Restriktor steuert die Analysetiefe, anhand der Länge von catIndex.
        Beispiel: catIndex = 1_25_1_18, len(catIndex) = 8. if len(catIndex) < restrictor ...
        
        BigError: Wenn der htmlcode nicht verarbeitet werden kann, (d.h. die funktion erwartet X Seiten, 
        aber sie können nicht aus dem htmlcode extrahiert werden oder sie sind über mehrere Seiten der
        aktuellen Seite verteilt, dieser Fall wird von meinem Skript nicht berücksichtigt) dann wird 
        der letzte Schritt wiederholt, das führt zu doppelter UnterkategorieAnalyse und somit 
        zu doppelten Einträgen der aktuellen Unterkategorie, was durch try/except und ein unterkategorienDict 
        verhindert wird. """

    print catIndex, category # nur dass man sieht, dass etwas passiert
    restrictor = int(9)

    # hole den Quelltext der aktuellen url
    siteSoup = BeautifulSoup(getWikiPage(url))

    # analysier die zum Link gehörende Info
    #print info
    nextStep = parse_info_for_next_step(info)
    unterkategorien = int(nextStep[0][0]); seiten = int(nextStep[0][1]); dateien = int(nextStep[0][2])


    if dateien > 0: #    dateien > 200 nur über /w/index.php? Weiterleitung zu erreichen ...
        # frühstmöglicher Abbruch
        if len(catIndex) >= restrictor:
            1+1 # do nothing
            #print 'len(index) cross restrictor boundary: ',restrictor,'len(index):', len(catIndex)                

        else:
            try:
                fileSoup = siteSoup.findAll(id="mw-category-media")
                files = getFiles(str(list(fileSoup)[0]))
                (analysFiles,fileString) = prettyfyFiles(files)

                if dateien > 200:
                    dateiTerminale.write(str(catIndex)+' '+category+' \t'+fileString+' ... \n\n')
                    #print dateien,'Datei(en), davon', analysFiles, 'analysiert! ...',dateien - analysFiles ,'weitere Dateien'
                else:
                    dateiTerminale.write(str(catIndex)+' '+category+' \t'+fileString+'\n\n')
                    #print analysFiles, 'Datei(en) analysiert! '

            # um Reanalyse zu erkennen
            #dateienDict[catIndex] = category

            except:
            #try:
                #dateienDict[catIndex]
                #print catIndex, dateienDict[catIndex],' > ', dateien, 'Datei(en) wurde(n) schon analysiert! '
            #except:
                dateiTerminale.write(str(catIndex)+' '+category+' \t'+'can not get the '+str(dateien)+' file(s)'+'\n\n')
                print dateien,'Datei(en) konnten nicht analysiert werden! -------------------> ErrorLog '
                dateiErrorLog.write('dateienTaskError: '+str(catIndex)+', '+category+', '+url+', '+info+'\n\n')


    if seiten > 0: #    seiten > 200 nur über /w/index.php? Weiterleitung zu erreichen ... 

        if len(catIndex) >= restrictor:
            1+1 # do nothing
            #print 'len(index) cross restrictor boundary: ',restrictor,'len(index):', len(catIndex)               

        else:
            try:
                pagesSoup = siteSoup.findAll(id="mw-pages")
                pages = getPages(str(list(pagesSoup)[0]))
                (analysPages,pageString) = prettyfyPages(pages)

                if seiten > 200:
                    dateiTerminale.write(str(catIndex)+' '+category+' \t'+pageString+' ... \n\n')
                    #print seiten,'Seite(n), davon', analysPages, 'analysiert! ...',seiten - analysPages ,'weitere Seiten'
                else:
                    dateiTerminale.write(str(catIndex)+' '+category+' \t'+pageString+'\n\n')
                    #print analysPages, 'Datei(en) analysiert! '

            # um Reanalyse zu erkennen
            #seitenDict[catIndex] = category

            except:
            #try:
                #seitenDict[catIndex]
                #print catIndex, seitenDict[catIndex],' > ', seiten, 'Seite(n) wurde(n) schon analysiert! '
            #except:

                # falls der htmlcode nicht analysiert werden konnte
                # wird der Vollständigkeit halber dennoch eingetragen und kann im ErrorLog ausführlich analysiert werden 
                dateiTerminale.write(str(catIndex)+' '+category+' \t'+'can not get the '+str(seiten)+' page(s)'+'\n\n')
                print seiten,'Seite(n) konnten nicht analysiert werden! -------------------> ErrorLog '

                dateiErrorLog.write('seitenTaskError: '+str(catIndex)+', '+category+', '+url+', '+info+'\n\n')


    if unterkategorien > 0:

        # restrictor, because no one lives forever ...
        # allerdings verursacht, doppeltes analysieren auf der vorletzten Stufe > einmal get x! & einmal can not get x!
        if len(catIndex) < restrictor:

            try:
                subcatSoup = siteSoup.findAll(id="mw-subcategories")
                subcats = getSubcats(str(list(subcatSoup)[0]), catIndex)
                (analysSubcats,subcatString) = prettyfySubcatListe(subcats)

                if unterkategorien > 200:
                    dateiPfade.write(str(catIndex)+' '+category+' \t'+subcatString+' ... \n\n')
                    #print Unterkategorien,'Unterkategorie(n), davon', analysSubcats, 'analysiert! ...',unterkategorien - analysSubcats ,'weitere Unterkategorien'
                else:
                    dateiPfade.write(str(catIndex)+' '+category+' \t'+subcatString+'\n\n')
                    #print analysSubcats, 'Unterkategorie(n) analysiert! '

                # um Doppelung durch Fehlanalysen zu erkennen
                unterkategorienDict[catIndex] = category

                ################################                    
                start_recursive_wrapper(subcats)
                ################################

            except:
                try:
                    unterkategorienDict[catIndex]
                    print catIndex, unterkategorienDict[catIndex],' > ', unterkategorien, 'Unterkategorie(n) wurde(n) schon analysiert! '

                except:
                    if len(index) >= restrictor:
                        print 'len(index) cross restrictor boundary: ',restrictor,'len(index)', len(index) # do nothing
                    else:
                        # falls der htmlcode nicht analysiert werden konnte
                        # wird der Vollständigkeit halber dennoch eingetragen und kann im ErrorLog ausführlich analysiert werden 
                        dateiPfade.write(str(catIndex)+' '+category+' \t'+'can not get the '+str(unterkategorien)+' cat(s)'+'\n\n')
                        print unterkategorien,'Unterkategorie(n) konnten nicht analysiert werden! -------------------> ErrorLog '

                        dateiErrorLog.write('unterkategorienTaskError: '+str(catIndex)+', '+category+', '+url+', '+info+'\n\n')


    # macht das skript langsam, ist aber auch nicht ganz irrelevant ...
    if unterkategorien == 0 and seiten == 0 and dateien == 0:
        #print (str(catIndex)+' '+category+' \tDiese Kategorie ist leer.')
        dateiTerminale.write(str(catIndex)+' '+category+' \tDiese Kategorie ist leer.\n\n')



##############################################################################################
##############################################################################################
##############################################################################################


def start_recursive_wrapper(subcats):
    """ Diese Funktion beinhaltet lediglich eine while-Schleife, die jede Unterkategorie
        an den wrapper(catIndex,category,addUrl,info) schickt, bis die Liste der Unterkategorien
        und jede Liste dieser Unterkategorien ... abgearbeitet ist. 
        Das passiert entweder nie, da das wiki reagiert und man in eine Schleife gerät
        (s. u. Angriff1 - Auswertung) oder die Länge des catIndex wird restringiert. """

    countUp = 0
    countDown = len(subcats)
    # jetzt analysiere jede Unterkategorie, bis es keine mehr gibt
    while countDown != 0:
        (catIndex,category,addUrl,info) = subcats[countUp]
        wrapper(catIndex,category,mainUrl+addUrl,info)
        countDown -= 1
        countUp += 1



def initialStep():
    """ Die Startseite wird aufgerufen und sowohl die Seiten als auch die Unterkategorien
        der Hauptkategorie/Startseite werden in dieser Funktion ermittelt. (der Übersichlichkeit halber
        wird das in einer Art Vorstufe gemacht)
        Am Ende wird start_recursive_wrapper(mit der Liste der Unterkategorien) aufgerufen. """

    htmlcode = getWikiPage('http://de.wikipedia.org/wiki/Kategorie:!Hauptkategorie')
    siteSoup = BeautifulSoup(htmlcode)

    pagesSoup = siteSoup.findAll(id="mw-pages")
    pages = getPages(str(list(pagesSoup)[0]))
    dateiTerminale.write('1 !Hauptkategorie \t'+pages[0][1]+'\n\n')

    subcategoriesSoup = siteSoup.findAll(id="mw-subcategories")
    subcats = getSubcats(str(list(subcategoriesSoup)[0]),1)
    (length,subcatString) = prettyfySubcatListe(subcats)
    dateiPfade.write('1 !Hauptkategorie \t'+subcatString+'\n\n')

    # here it goes :)
    start_recursive_wrapper(subcats)




#   Hauptteil _______________________________________________________________________________________


if __name__ == "__main__":

    """ hier startet das skript > mainUrl = 'http://de.wikipedia.org' + ? 
        alle drei Dateien (DateiPfade, DateiTerminale, ErrorLog) werden geöffnet
        und es folgt der initialStep """

    mainUrl = 'http://de.wikipedia.org'


    # um Doppelung durch Fehlanalysen zu erkennen
    unterkategorienDict = {}

    # öffnen der Zieldateien
    dateiPfade = open('aufgabe_8_output/DateiPfade','a+')
    dateiTerminale = open('aufgabe_8_output/DateiTerminale','a+')
    dateiErrorLog = open('aufgabe_8_output/ErrorLog','a+')

    initialStep()
    dateiPfade.close()
    dateiTerminale.close()
    dateiErrorLog.close()
# _______________________________________________________________________________________________ EOF