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>
113 lines
3.4 KiB
Python
Executable File
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()
|