Files
brevetcard/generate_cards.py
Peter Adam b0e1dde22f Add CSV-based personalized card generation
Add template-based system for generating personalized brevet cards
from CSV data. Uses proper separation of concerns with template file
and Python script.

- Add brevetkarte-template.tex with placeholders
- Add generate_cards.py to read CSV and populate template
- Update Makefile with generate-personalized and build-personalized targets
- Update .gitignore to exclude generated files

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 14:47:56 +01:00

113 lines
3.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Generate personalized brevet cards from CSV data.
"""
import csv
import sys
from pathlib import Path
def escape_latex(text):
"""Escape special LaTeX characters."""
if not text:
return ""
replacements = {
'&': r'\&',
'%': r'\%',
'$': r'\$',
'#': r'\#',
'_': r'\_',
'{': r'\{',
'}': r'\}',
'~': r'\textasciitilde{}',
'^': r'\textasciicircum{}',
'\\': r'\textbackslash{}',
}
result = str(text)
for char, replacement in replacements.items():
result = result.replace(char, replacement)
return result
def generate_card_from_template(template, data):
"""Replace placeholders in template with participant data."""
name = f"{escape_latex(data['Vorname'])} {escape_latex(data['Nachname'])}"
street = escape_latex(data['Straße'])
plz_ort = f"{escape_latex(data['PLZ'])} {escape_latex(data['Ort'])}"
land = escape_latex(data['Land'])
medaille = "Ja" if data['Medaille'].lower() == 'ja' else "Nein"
startnr = escape_latex(data['Startnr'])
# Replace placeholders
card = template.replace('{{NAME}}', name)
card = card.replace('{{STREET}}', street)
card = card.replace('{{PLZ_ORT}}', plz_ort)
card = card.replace('{{LAND}}', land)
card = card.replace('{{MEDAILLE}}', medaille)
card = card.replace('{{STARTNR}}', startnr)
return card
def main():
csv_file = Path("Export Brevetkarte.csv")
template_file = Path("brevetkarte-template.tex")
output_file = Path("brevetkarte-personalized.tex")
if not csv_file.exists():
print(f"Error: {csv_file} not found!", file=sys.stderr)
sys.exit(1)
if not template_file.exists():
print(f"Error: {template_file} not found!", file=sys.stderr)
sys.exit(1)
# Read template
print(f"Reading template from {template_file}...")
template = template_file.read_text(encoding='utf-8')
# Read CSV data
print(f"Reading participant data from {csv_file}...")
participants = []
with open(csv_file, 'r', encoding='utf-8-sig') as f: # utf-8-sig handles BOM
reader = csv.DictReader(f)
for row in reader:
if row['Startnr']: # Skip empty rows
participants.append(row)
if not participants:
print("No participants found in CSV file!", file=sys.stderr)
sys.exit(1)
print(f"Found {len(participants)} participant(s)")
# Generate document with all cards
cards = []
for i, participant in enumerate(participants):
card = generate_card_from_template(template, participant)
cards.append(card)
# Combine cards with spacing/page breaks
document_parts = []
for i, card in enumerate(cards):
document_parts.append(card)
# Add vertical space between cards on same page
if i % 2 == 0 and i < len(cards) - 1:
document_parts.append("\n\\vspace{0.8cm}\n\n")
# Add page break after every 2 cards (except at the end)
if i % 2 == 1 and i < len(cards) - 1:
document_parts.append("\n\\newpage\n\n")
# Close document
document_parts.append("\n\\end{document}\n")
# Write output
document = ''.join(document_parts)
output_file.write_text(document, encoding='utf-8')
print(f"Generated {output_file}")
print(f"Total pages: {(len(participants) + 1) // 2}")
print(f"Compile with: make build-personalized")
if __name__ == "__main__":
main()