Einführung
Für ein gemeinschaftliches Abendessen wird für jede Person eine Einladung benötigt. Du erzeugst aus einer CSV-Gästeliste, JSON-Veranstaltungsdaten und einer vorbereiteten HTML-Vorlage einen neuen Satz persönlicher Einladungsdateien. Personen mit gleichem Namen müssen getrennte Dateien erhalten. Namen wie Zoë, "Z" & <Friends> sollen als Text erscheinen.
Du solltest Python-Zeichenketten, Wörterbücher, Schleifen und Funktionen sowie die grundlegende Terminalbedienung kennen. Diese Übung erklärt das Lesen von Dateien, CSV-Datensätze, Vorlagen, das Maskieren von HTML-Text und neue Ausgabeverzeichnisse. Kommandozeilenoberfläche und Gestaltung sind vorbereitet. Du implementierst Feldzuordnung, vollständige Prüfung und individuelle Erzeugung. CSS, E-Mail-Versand und PDF-Erstellung gehören nicht dazu. Vom Autor geschätzte Dauer: 55–75 Minuten einschließlich Lesen und gewöhnlicher Fehler.
Öffne deine erste Einladung
In diesem Schritt erzeugst du eine normale Einladung und ordnest ihren Inhalt den Eingabefeldern zu. Öffne in WebIDE das Menü Terminal → New Terminal und warte auf die Shell-Eingabeaufforderung von labex. In schmalen Fenstern kann Terminal unter More (...) liegen. Alle Befehle werden in diesem Terminal ausgeführt. Der vorbereitete Arbeitsbereich ist unabhängig von früheren Übungen.
cd wechselt das aktuelle Verzeichnis. Ein absoluter Pfad beginnt mit /; relative Pfade werden vom aktuellen Verzeichnis aus aufgelöst. Findet ein späterer Befehl nach einem Verzeichniswechsel eine Datei nicht mehr, kehre mit demselben Befehl hierher zurück:
cd /home/labex/project/invitations
cat zeigt eine Textdatei an. Lies die Veranstaltungsdaten:
cat event.json
JSON verwendet geschweifte Klammern für Objekte und doppelte Anführungszeichen für Zeichenkettenschlüssel und -werte. Diese vier Werte werden Titel, Datum, Uhrzeit und Ort der Einladung. Das ausführbare Beispiel one.py ordnet sie bereits zusammen mit einem Gast der bereitgestellten Gestaltung zu. python3 führt eine Python-Datei aus. Erzeuge das Beispiel im neuen Verzeichnis batches/first:
python3 one.py
Erwartete Ausgabe:
Created batch: batches/first
Wechsle vom Plattform-Tab WebIDE zu Web 8080. Die Vorbereitung hat den Einladungsbetrachter bereits gestartet; Go Live ist nicht erforderlich. Aktualisiere die Vorschau und suche first/g001.html. Open invitation öffnet die echte HTML-Datei in einem neuen Browser-Tab. Lies Maya Patel, The Long Table, Uhrzeit und Ort. Schließe diesen Browser-Tab, um zu Web 8080 zurückzukehren. Download HTML speichert dieselbe Datei für die Verwendung außerhalb der Übung.

Die Anrede nennt den Gast; die beiden unteren Bereiche zeigen Uhrzeit und Veranstaltungsort.
Die Vorschau liest ausschließlich tatsächlich vorhandene Dateien in batches. Sie erzeugt keine fehlenden Einladungen. Das frühere batches/archive bleibt als vorhandenes Arbeitsergebnis erhalten. Dein fertiges Programm erstellt neue Stapel und bewahrt diese Dateien sowie alle Eingaben. Wechsle für den nächsten Schritt zurück zu WebIDE.
Ordne CSV-Datensätze dem HTML-Text zu
In diesem Schritt lässt du den wiederverwendbaren Renderer jeden Gästewert in den richtigen Textbereich einsetzen. Kehre zum selben WebIDE-Terminal und Arbeitsbereich zurück. Die folgenden relativen Pfade beginnen in /home/labex/project/invitations.
Lies Datensätze, statt an Kommas zu teilen
CSV ist ein Tabellenformat. Die erste Zeile enthält Feldnamen. Ein Komma trennt Felder, außer es steht innerhalb eines Feldes in Anführungszeichen. Ein Anführungszeichen innerhalb eines solchen Feldes wird verdoppelt. Deshalb kann split(",") nicht jede Zeile richtig einlesen. Lies die bereitgestellte Liste:
cat guests.csv
Es gibt zwei Datensätze für Alex Chen mit unterschiedlichen guest_id-Werten. Der Name des letzten Gastes enthält ein Komma, Anführungszeichen, & und spitze Klammern. All dies sind gültige Namenszeichen. Eine stabile ID identifiziert eine Person; der Anzeigename reicht dafür nicht aus.
Das vorbereitete generate.py liest diese Datensätze. Lies es, bevor du den Renderer änderst:
cat generate.py
with ... as source öffnet eine Datei und schließt sie nach dem eingerückten Block. encoding="utf-8" erhält Unicode-Zeichen. newline="" überlässt dem CSV-Leser die Behandlung der Datensatzgrenzen. csv.DictReader(source) erstellt anhand der Kopfzeile ein Wörterbuch pro Datensatz; list(...) liest die Datensätze in eine Liste. Beispielsweise ist guest["name"] ein vollständiger Name einschließlich eines darin enthaltenen Kommas. json.loads(...) wandelt den JSON-Veranstaltungstext in ein Wörterbuch um. Diese Leser und die Kommandozeilenoberfläche sind bereits implementiert.
Unterscheide CSV-Anführungszeichen und HTML-Maskierung
HTML verwendet <...> für Tags und &...; für Zeichenreferenzen. CSV-Anführungszeichen schützen die Tabellenstruktur. Sie verhindern nicht, dass ein Name wie <Friends> als HTML interpretiert wird. html.escape(value, quote=True) wandelt Sonderzeichen in Referenzen um, die der Browser als ursprünglichen Text anzeigt. Hier wird jeder Wert in einen HTML-Textbereich eingesetzt. Dies ist keine allgemeine Anleitung zum Einfügen in beliebiges JavaScript, CSS oder URLs.
Template verwendet $name, $title und die weiteren $field-Platzhalter aus invitation.html. substitute(mapping) ersetzt jeden Platzhalter durch den zugehörigen Wörterbuchwert und weist fehlende Zuordnungen zurück. Die sechs Markierungen im Dokumentkörper, data-field="name", message, title, date, time und venue, bezeichnen die semantischen Textbereiche. Jeder muss genau einmal mit dem passenden Text erhalten bleiben. Verändere die bereitgestellte Eingabevorlage nicht. Gleichwertige Tags, Anordnungen und Entity-Schreibweisen in der erzeugten Ausgabe werden akzeptiert. Ein Name nur im <title> der Seite füllt seinen Bereich im Dokumentkörper nicht aus.
Erstelle den folgenden Renderer. cat > rendering.py schreibt eine Datei und ersetzt ihren bisherigen Inhalt. Die in Anführungszeichen gesetzte Here-Document-Begrenzung <<'PYCODE' behandelt alles bis zum abschließenden PYCODE als wörtlichen Text. Die Abschlussmarke muss allein in einer Zeile stehen. Nur dein Renderer wird ersetzt, die Eingaben bleiben unverändert.
Das Wörterbuch ordnet Gast- und Veranstaltungsfelder ausdrücklich zu. values.items() liefert Schlüssel-Wert-Paare. Die Dictionary Comprehension {key: ... for key, value in values.items()} maskiert jeden Wert und behält den Schlüssel. from html import escape importiert die Funktion aus der Standardbibliothek.
cat > rendering.py <<'PYCODE'
from html import escape
def render_guest(guest, event, template):
values = {"name": guest["name"], "message": guest["message"],
"title": event["title"], "date": event["date"],
"time": event["time"], "venue": event["venue"]}
return template.substitute({key: escape(value, quote=True)
for key, value in values.items()})
PYCODE
Die Funktion liefert HTML-Text und erstellt keine Dateien. Lokale Hilfsmodule dürfen .py-Dateien neben generate.py sein; Paketunterverzeichnisse liegen außerhalb dieser Übung. Die Maskierung erfolgt genau einmal vor der Ersetzung. So zeigt der Browser & an und nicht den sichtbaren Text &. Gestaltung und persönlicher Text der Einladung sind nun getrennt.
Prüfe alle Gäste vor dem Erstellen eines Stapels
In diesem Schritt erzeugst du eine Datei je ID und schützt dabei frühere Arbeit. Bleibe im selben WebIDE-Terminal.
Das ursprüngliche batch.py verwendet Namen als Dateinamen und erlaubt ein vorhandenes Verzeichnis. Zwei Personen namens Alex Chen können ihre Dateien gegenseitig überschreiben. Lies die kleine Funktion:
cat batch.py
Der Vertrag verlangt ein neues Stapelverzeichnis mit <guest_id>.html für jede Zeile. IDs enthalten nur kleine ASCII-Buchstaben, Ziffern und Bindestriche. Namen und Nachrichten bleiben UTF-8-Text ohne diese Zeichenbeschränkung. Pflichtfelder des Gastes sind guest_id, name, message; die Veranstaltung benötigt title, date, time, venue. Leere oder nur aus Leerraum bestehende Pflichtwerte, doppelte IDs und bereits vorhandene Ausgabeverzeichnisse müssen mit einer hilfreichen Fehlermeldung abgewiesen werden. CSV, beide Veranstaltungsdateien, Vorlage und batches/archive bleiben unverändert.
Trenne Vorbereitung und Schreiben
Prüfe zuerst alle Datensätze und rendere alle Seiten. Erst wenn das vollständig gelingt, dürfen Verzeichnis und Dateien erstellt werden. Andernfalls könnte ein fehlerhafter letzter Datensatz einen irreführenden Teilstapel hinterlassen. Das schützt vor den hier behandelten Eingabefehlern, garantiert aber keine Transaktion bei Festplattenfehlern oder Stromausfall.
isinstance(value, str) prüft, ob ein Wert Text ist. event.get(field) liefert bei einem fehlenden Schlüssel None, sodass eine verständliche Fehlermeldung möglich ist. strip() prüft auf Inhalt außer Leerraum, ohne den später gerenderten Text zu verändern. raise ValueError(...) meldet ungültige Eingaben. Die vorbereitete Oberfläche fängt den Fehler ab, gibt Cannot generate invitations: ... aus und beendet sich erfolglos.
Ein set() speichert bereits gesehene IDs. in prüft die Zugehörigkeit; seen.add(...) merkt sich eine ID. all(...) prüft jedes Zeichen gegen die angekündigte Zeichenregel. pages.append((guest_id, page)) speichert ein ID-Seiten-Paar. for guest_id, page in pages entpackt diese Paare.
Path(output) stellt das angeforderte Verzeichnis dar. mkdir() erstellt es und weist vorhandene Pfade zurück, weil wir kein exist_ok=True übergeben. Der Operator / verbindet Pfade. Ein f-String wie f"{guest_id}.html" setzt die ID in einen Dateinamen ein. write_text(..., encoding="utf-8") schreibt den vollständigen HTML-Text als UTF-8.
Ersetze ausschließlich batch.py durch diese Kernimplementierung:
cat > batch.py <<'PYCODE'
from pathlib import Path
from rendering import render_guest
def write_batch(guests, event, template, output):
for field in ("title", "date", "time", "venue"):
if not isinstance(event.get(field), str) or not event[field].strip():
raise ValueError(f"Missing event field: {field}")
if not guests:
raise ValueError("The guest list is empty")
seen = set()
pages = []
for guest in guests:
for field in ("guest_id", "name", "message"):
if not isinstance(guest.get(field), str) or not guest[field].strip():
raise ValueError(f"Missing guest field: {field}")
guest_id = guest["guest_id"]
if not all(character in "abcdefghijklmnopqrstuvwxyz0123456789-" for character in guest_id):
raise ValueError("Guest IDs must use lowercase letters, digits, or hyphens")
if guest_id in seen:
raise ValueError(f"Duplicate guest ID: {guest_id}")
seen.add(guest_id)
pages.append((guest_id, render_guest(guest, event, template)))
output = Path(output)
output.mkdir() ## An existing batch is always refused.
for guest_id, page in pages:
(output / f"{guest_id}.html").write_text(page, encoding="utf-8")
PYCODE
from rendering import render_guest verwendet deinen lokalen Renderer. Die erste Schleife prüft die Veranstaltungsfelder, die nächste validiert die Gäste und bereitet Seiten vor, und die letzte schreibt den vollständigen Stapel. Vor Abschluss von Prüfung und Rendering wird keine Datei erstellt.
Die Oberfläche nimmt vier Positionspfade entgegen: Gäste-CSV, Veranstaltungs-JSON, HTML-Vorlage und neues Ausgabeverzeichnis, in dieser Reihenfolge. Führe sie mit der vollständigen Liste aus:
python3 generate.py guests.csv event.json invitation.html batches/evening
Erwartete Ausgabe:
Created batch: batches/evening
ls listet die Verzeichniseinträge auf:
ls batches/evening
Du solltest g101.html, g102.html und g103.html finden. Die beiden Alex-Einladungen haben nun unabhängige IDs. Jede Datei enthält die eigene Nachricht der Person. Die dritte behält außerdem Komma, Anführungszeichen, Unicode und wörtliche spitze Klammern.
Verwende den Generator erneut und bewahre frühere Arbeit
In diesem Schritt nutzt du dasselbe Programm für eine weitere Veranstaltung und übst die Behebung gewöhnlicher Fehler. Beginne im selben WebIDE-Terminal. Die folgenden Befehle sind einzeln auszuführen; ein erwarteter Fehler verhindert daher nicht die Fortsetzung.
Wiederhole zuerst bewusst den bereits fertigen Ausgabepfad. Dies soll fehlschlagen: mkdir() weist einen vorhandenen Stapel zurück. Die Meldung beginnt mit Cannot generate invitations: und enthält einen Hinweis auf eine vorhandene Datei. Der frühere Stapel muss unverändert bleiben.
python3 generate.py guests.csv event.json invitation.html batches/evening
Lies das vorbereitete Beispiel mit doppelter ID. Der zweite Gast hat dieselbe ID wie der erste:
cat invalid-duplicate.csv
Versuche, damit einen neuen Stapel anzulegen. Auch dieser Fehler ist erwartet: Noch vor der Verzeichniserstellung erscheint Duplicate guest ID: g101:
python3 generate.py invalid-duplicate.csv event.json invitation.html batches/rejected-duplicate
Das Beispiel ohne Nachricht schlägt absichtlich mit Missing guest field: message fehl. Obwohl die ersten Datensätze gültig sind, darf kein Teilstapel entstehen:
python3 generate.py invalid-missing.csv event.json invitation.html batches/rejected-missing
Ein falsch geschriebener Eingabepfad ist ein anderer Fall: Das Betriebssystem kann die Datei nicht öffnen. Der nächste Befehl verwendet absichtlich das nicht vorhandene guest.csv. Lies den Fehler und setze die Umgebung nicht zurück:
python3 generate.py guest.csv daytime.json invitation.html batches/daytime
Behebe den Fehler mit dem tatsächlichen Pfad guests.csv. Lies die zweite Veranstaltung und erzeuge mit demselben Programm und derselben Gästeliste einen neuen Stapel:
cat daytime.json
python3 generate.py guests.csv daytime.json invitation.html batches/daytime
Erwartete Ausgabe:
Created batch: batches/daytime
Wechsle zu Web 8080 und aktualisiere. Öffne nacheinander evening/g101.html und evening/g102.html. Beide haben denselben Namen, aber unterschiedliche Nachrichten. Öffne evening/g103.html: Komma, Anführungszeichen, ë, kaufmännisches Und und <Friends> müssen als wörtlicher Text erscheinen. Öffne daytime/g103.html und lies neuen Titel, Uhrzeit und Ort. Schließe jeden Einladungs-Tab, um zur Liste zurückzukehren. Lade die Tageseinladung mit Download HTML herunter und öffne die Datei. Ihr persönlicher Text muss mit der Vorschau übereinstimmen.

Der Name behält Komma, Anführungszeichen und spitze Klammern. Sie erscheinen sichtbar in der Anrede.
Die beiden Veranstaltungsstapel sind unabhängig. CSV, Veranstaltungsdateien und Vorlage sind unverändert, das alte Archiv ist weiterhin vorhanden. Ungültige Eingaben, vorhandene Stapel und fehlende Eingabepfade haben unterschiedliche Ursachen. Jeder Fall lässt sich korrigieren, ohne frühere Einladungen zu zerstören.
Zusammenfassung
Du hast aus CSV-Datensätzen und JSON-Veranstaltungsdaten echte, herunterladbare HTML-Einladungen erzeugt. Stabile IDs wurden von Anzeigenamen getrennt, Felder den Textbereichen der Vorlage zugeordnet und Unicode sowie Sonderzeichen mit UTF-8 und HTML-Maskierung erhalten. Der gesamte Stapel wurde vor dem Schreiben geprüft. Der Generator kann nun neue Veranstaltungsstapel erstellen und dabei Eingaben sowie frühere Ergebnisse bewahren.



