Created
November 7, 2010 16:15
-
-
Save julians/666216 to your computer and use it in GitHub Desktop.
Nimmt diese komische SPLUS-Datei und generiert daraus eine CSV-Datei mit allen Veranstaltungsterminen. »make_date_csv.py --in=2010-SoSe-20100809-V.utf-8.txt --out=blah«
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
# Die erste Zeile sagt dem Terminal, dass das hier Python-Code ist. | |
# Die zweite Zeile sagt dem Python-Interpreter: »Hey, hier gibt’s Unicode, keine Angst« – | |
# sonst stürzt er ab, falls man irgendwelche Sonderzeichen im Code oder den Kommentaren verwendet. | |
# Man braucht keine der beiden Zeilen, wenn man zu faul zum Tippen ist und nur ASCII benutzt. | |
import sys | |
# getopt ist für die Kommandozeilenargumente zuständig | |
import getopt | |
# re ist das mit den Regular Expressions | |
import re | |
# Codecs brauchen wir, um die utf-8 Datei lesen zu können | |
import codecs | |
# Na was das wohl macht … | |
import csv | |
from datetime import datetime, timedelta | |
from collections import defaultdict | |
from operator import itemgetter | |
from dateutil import parser as dateparser | |
from utils.date import GermanParserInfo | |
# Die Hauptfunktion, die wir beim Start des Programmes aufrufen – siehe ganz unten. | |
# Könnte natürlich auch »deine_mutter« oder so heißen | |
def main(argv): | |
# Wir versuchen mal, die Kommandozeilenargumente zu parsen | |
try: | |
opts, args = getopt.getopt(argv, "", ["in=", "out="]) | |
except getopt.GetoptError: | |
print "Crap, invalid arguments!" | |
sys.exit(2) | |
for opt, arg in opts: | |
# Hier loopen wir gerade durch die Argumente und schauen, ob was für uns dabei ist | |
if opt in ("--in"): | |
filename_in = arg | |
if opt in ("--out"): | |
filename_out = arg | |
try: | |
filename_out.index("csv") | |
except: | |
filename_out += ".csv" | |
# Nun versuchen wir, die Datei zu öffnen und zu lesen | |
try: | |
raw_text = codecs.open(filename_in, "r", "utf-8").read() | |
except: | |
print "Oh noes, no input file!" | |
sys.exit(1) | |
blah = parse_splus(raw_text) | |
# Duplikate und Schwachsinn rausfiltern | |
# TODO: Ich bin mir nicht sicher, ob die Duplikate wirklich alle Duplikate sind, | |
# und ob der Schwachsinn wirklich schwachsinnig ist. | |
ids = {} | |
def dedupe(row): | |
if not "R: Primärschlüssel Räume" in row or row["V: Primärschlüssel Veranstaltung"] in ids: | |
return False | |
else: | |
ids[row["V: Primärschlüssel Veranstaltung"]] = True | |
return True | |
rows = filter(dedupe, blah) | |
# Ende Duplikate und Schwachsinn rausfiltern | |
events = [] | |
for row in rows: | |
events += create_events(row) | |
try: | |
csv_file = csv.writer(open(filename_out, 'wb'), delimiter=';') | |
except: | |
print "Could not write output file" | |
csv_file.writerow([key.encode("utf-8") for key in events[0].keys()]) | |
for event in events: | |
csv_file.writerow([value.encode("utf-8") if isinstance(value, basestring) else value for value in event.values()]) | |
def parse_splus(raw_text): | |
# Die Namen der Felder, die in der Quelldatei stehen | |
field_names = [field_name.strip() for field_name in "FB: Primärschlüssel FB;FB: Name;FB: Beschreibung;FB:Textfeld 1 ;FB: Textfeld 2 ;FB: Textfeld 3;FB: Textfeld 4 ;FB: Textfeld 5 ;M: Primärschlüssel Modul;M: Beschreibung;M: Name;M: Textfeld 1 ;M: Textfeld 2 ;M: Textfeld 3 ;M: Textfeld 4 ;M: Textfeld 5 ;M: Pflicht Semester StS-Primärschlüssel ;M: Wahlpflicht Semester StS-Primärschlüssel ;M: Primärschlüssel Flaggen;M: Beschreibung Flaggen ;M: Textfeld 1 der Flaggen;M 2: Textfeld 2 der Flaggen;M: Textfeld 3 der Flaggen ;M: Textfeld 4 der Flaggen ;M: Textfeld 5 der Flaggen ;M: Primärschlüssel Semester;S: Primärschlüssel Semester ;S: Beschreibung ;S:Textfeld 1 ;S: Textfeld 2 ;S: Textfeld 3 ;S: Textfeld 4 ;S: Textfeld 5;V: Primärschlüssel Veranstaltung;V: Beschreibung ;V: Num. ;V: Alpha-num.;V: Textfeld 1 ;V: Textfeld 2 ;V: Textfeld 3;V: Textfeld 4 ;V: Tag;V: Anfang;V: Ende ;V: Primärschlüssel Flaggen;V: Beschreibung Flaggen;V: Textfeld 1 der Flaggen ;V: Textfeld 2 der Flaggen;V: Textfeld 3 der Flaggen ;V: U-Wochen als Anfangswoche;V: U-Wochen als Anfangsdatum bis Enddatum;A: Primärschlüssel Art ;A: Name Art ;D: Primärschlüssel Dozent ;D: Beschreibung Dozent;D: Name Dozent ;D: Email-Adresse ;D: Eigenschaft Dozent ;D: Textfeld 1 ;D: Textfeld 2 ;D: Textfeld 3 ;D: Textfeld 4;D:Textfeld 5 ;StS: Primärschlüssel Studenten-Sets ;StS: Name zugeordneter Studenten-Sets;StS: leer - Doppelbuchungen ;StS: leer Doppelbuchungen;StS: leer - Doppelbuchungen ;StS: leer - Doppelbuchungen ;StS: leer - Doppelbuchungen ;StS: leer - Doppelbuchungen;R: Primärschlüssel Räume;R: Name;R: Beschreibung ;R: Primärschlüssel Flaggen ;R: Name Flaggen Räume ;R: Beschreibung Flaggen der Räume ;R: Textfeld 1 Flaggen der Räume ;R: Textfeld 2 Flaggen der Räume ;R: Textfeld 3 Flaggen der Räume;R: Textfeld 4 der Flaggen Räume ;R: Textfeld 5 der Flaggen Räume ;Doppelbuchungen der Veranstaltungen als !".split(";")] | |
# Regex, um die ganze große fette Datei in die einzelnen Veranstaltungen zu splitten | |
# (raw string notation: r"", so dass man nicht zigtausend Backslashes escapen muss | |
# und man den String über mehrere Zeilen schreiben kann) | |
entry_pattern = re.compile(r""" | |
^#SPLUS[a-z0-9]{6} # Zeilenanfang, gefolgt von #SPLUS und sechs Buchstaben oder Zahlen | |
.+? # alles mögliche, aber nicht-greedy | |
\\!?$ # ein Backslash, direkt gefolgt von: null oder ein Ausrufezeichen, | |
# dann Zeilenende | |
""", re.IGNORECASE | re.MULTILINE | re.UNICODE | re.DOTALL | re.VERBOSE) | |
rows = [] | |
# So, nun loopen wir durch die Veranstaltungen | |
for match in entry_pattern.finditer(raw_text): | |
rows.append({}) | |
# Splitten die wiederum in die einzelnen Felder | |
for i, field in enumerate(match.group().split("\\")): | |
rows[-1][field_names[i]] = field | |
return rows | |
def create_events(row): | |
# Erstmal nur ein paar Funktionen … | |
_parserinfo = GermanParserInfo() | |
def _create_events(row, i=0, j=0): | |
# Das Wort des Wochentages (z. B. »Montag«) in eine Zahl (0–6) umwandeln | |
weekday = [day[1] for day in GermanParserInfo.WEEKDAYS].index(row["V: Tag"].split(";")[i]) | |
# Anfangs- und Endzeit | |
start_time = _parse_time(row["V: Anfang"].split(";")[i]) | |
end_time = _parse_time(row["V: Ende"].split(";")[i]) | |
# Wenn die Endzeit 0 ist, dann ist damit Mitternacht des nächsten Tages gemeint | |
if end_time.hour == 0: end_time += timedelta(days=1) | |
duration = end_time - start_time | |
frequency = ( | |
14 if u"14-tägig" in row["V: Alpha-num."] else 7, # Tage zwischen Veranstaltungen | |
1 if u"unger. KW" in row["V: Alpha-num."] else 0 # ungerade Kalenderwochen? | |
) | |
# Start- und Enddatum nachschauen | |
start_date, max_date = [dateparser.parse(date, _parserinfo) for date in row["V: U-Wochen als Anfangsdatum bis Enddatum"].split("-")] | |
start_date = datetime.combine(start_date, start_time.time()) | |
# Den richtigen Wochentag in der ersten Woche finden | |
while start_date.weekday() != weekday: | |
start_date += timedelta(days=1) | |
# Jetzt noch wegen gerader bzw. ungerader Kalenderwoche gucken | |
if start_date.isocalendar()[1] % 2 != frequency[1]: start_date += timedelta(weeks=1) | |
# Und nun können wir die einzelnen Termine generieren | |
events = [] | |
while start_date <= max_date: | |
events.append({ | |
"start": start_date, | |
"end": start_date + duration, | |
"weekday": start_date.weekday(), | |
"week": start_date.isocalendar()[1], | |
"duration_minutes": (duration.days*24*60*60 + duration.seconds)/60, | |
"kind": row["A: Name Art"], | |
"event_id": row["V: Primärschlüssel Veranstaltung"], | |
"event_description": row["V: Beschreibung"], | |
"location_id": row["R: Primärschlüssel Räume"].split(";")[j], | |
"location_name": row["R: Name"].split(";")[j], | |
"location_description": row["R: Beschreibung"].split(";")[j], | |
"faculty_id": row["FB: Primärschlüssel FB"], | |
"faculty_name": row["FB: Name"], | |
"faculty_description": row["FB: Beschreibung"], | |
}) | |
start_date += timedelta(days=frequency[0]) | |
return events | |
# Diese Funktion parst einen String in der Form "xx:xx" in ein datetime-Objekt | |
def _parse_time(time_string): | |
return datetime(1, 1, 1, *[int(num) for num in time_string.split(":")]) | |
# Und hier jetzt Code, der sie aufruft | |
events = [] | |
# Manche Veranstaltungen finden an mehreren Terminen pro Woche statt | |
# – z. B. Blockveranstaltungen. | |
# Die Tage sind dann durch Semikolons separiert angegeben. | |
# Daher wird hier kurz geschaut, ob es mehrere Tage gibt, und wenn ja, | |
# für jeden Tag alle Termine fürs ganze Semester erstellt. | |
for i in range(0, len(row["V: Tag"].split(";"))): | |
for j in range(0, len(row["R: Primärschlüssel Räume"].split(";"))): | |
events += _create_events(row, i, j) | |
return events | |
# Der Teil des Skriptes, der ausgeführt wird, wenn wir es ausführen. | |
# Guckt, ob irgendwas irgendwas ist, und ruft dann die main-Funktion auf, | |
# übergibt als Argumente die Kommandozeilenargumente minus das erste, | |
# das der Dateiname sein sollte | |
if __name__ == "__main__": | |
main(sys.argv[1:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment