159 lines
5.4 KiB
Python
Executable File
159 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Generate personalized brevet cards from CSV and event config.
|
|
"""
|
|
import csv
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import yaml
|
|
except ImportError:
|
|
print("Error: PyYAML not installed. Run: pip install pyyaml", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
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 load_event_config(config_file):
|
|
"""Load event configuration from YAML file."""
|
|
with open(config_file, 'r', encoding='utf-8') as f:
|
|
return yaml.safe_load(f)
|
|
|
|
|
|
def apply_event_placeholders(text, config):
|
|
"""Replace event-level placeholders in a template string."""
|
|
event = config.get('event', {})
|
|
replacements = {
|
|
'{{EVENT_TITLE}}': escape_latex(event.get('title', '')),
|
|
'{{EVENT_KM}}': escape_latex(event.get('km', '')),
|
|
'{{EVENT_DATE}}': escape_latex(event.get('date', '')),
|
|
'{{EVENT_START}}': escape_latex(event.get('start_location', '')),
|
|
'{{EVENT_CLUB}}': escape_latex(event.get('club', '')),
|
|
'{{EVENT_CLUB_NR}}': escape_latex(event.get('club_nr', '')),
|
|
'{{EVENT_STARTZEIT}}': escape_latex(event.get('startzeit', '')),
|
|
}
|
|
for placeholder, value in replacements.items():
|
|
text = text.replace(placeholder, value)
|
|
return text
|
|
|
|
|
|
def generate_backside(template, config):
|
|
"""Fill back side template with cell content from event config."""
|
|
cells = config.get('backside', {})
|
|
result = template
|
|
for row in range(1, 4):
|
|
for col in range(1, 5):
|
|
key = f"{row}_{col}"
|
|
placeholder = f"{{{{CELL_{row}_{col}}}}}"
|
|
content = cells.get(key, "")
|
|
if content is None:
|
|
content = ""
|
|
result = result.replace(placeholder, content.strip())
|
|
return result
|
|
|
|
|
|
def generate_card_from_template(template, data):
|
|
"""Replace participant 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'])
|
|
|
|
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_brevetcard.csv")
|
|
template_file = Path("brevetkarte-template.tex")
|
|
backside_template_file = Path("brevetkarte-rueckseite-template.tex")
|
|
event_config_file = Path("event.yml")
|
|
output_file = Path("brevetkarte-personalized.tex")
|
|
backside_output_file = Path("brevetkarte-rueckseite.tex")
|
|
|
|
for f in [csv_file, template_file, backside_template_file, event_config_file]:
|
|
if not f.exists():
|
|
print(f"Error: {f} not found!", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print(f"Reading event config from {event_config_file}...")
|
|
event_config = load_event_config(event_config_file)
|
|
|
|
print(f"Reading template from {template_file}...")
|
|
template = template_file.read_text(encoding='utf-8')
|
|
template = apply_event_placeholders(template, event_config)
|
|
|
|
print(f"Reading participant data from {csv_file}...")
|
|
participants = []
|
|
with open(csv_file, 'r', encoding='utf-8-sig') as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
if row['Startnr']:
|
|
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 personalized front side
|
|
cards = []
|
|
for participant in participants:
|
|
card = generate_card_from_template(template, participant)
|
|
cards.append(card)
|
|
|
|
document_parts = []
|
|
for i, card in enumerate(cards):
|
|
document_parts.append(card)
|
|
if i % 2 == 0 and i < len(cards) - 1:
|
|
document_parts.append("\n\\vspace{0.8cm}\n\n")
|
|
if i % 2 == 1 and i < len(cards) - 1:
|
|
document_parts.append("\n\\newpage\n\n")
|
|
|
|
document_parts.append("\n\\end{document}\n")
|
|
output_file.write_text(''.join(document_parts), encoding='utf-8')
|
|
print(f"Generated {output_file}")
|
|
|
|
# Generate personalized back side
|
|
print(f"Reading back side template from {backside_template_file}...")
|
|
backside_template = backside_template_file.read_text(encoding='utf-8')
|
|
backside_output = generate_backside(backside_template, event_config)
|
|
backside_output_file.write_text(backside_output, encoding='utf-8')
|
|
print(f"Generated {backside_output_file}")
|
|
|
|
print(f"Total pages: {(len(participants) + 1) // 2}")
|
|
print(f"Compile with: make build-personalized")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |