Skip to content

Instantly share code, notes, and snippets.

@julians
Created November 7, 2010 16:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save julians/666216 to your computer and use it in GitHub Desktop.
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«
#!/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