Source code for sabbath_school_reproducer.generator.svg_updater

"""
Dynamic SVG Updater for Sabbath School Lessons

This module updates SVG files with dynamic content based on configuration.
"""

import re
import os
import tempfile
from datetime import datetime


[docs] class SvgUpdater: """Updates SVG files with dynamic content based on configuration.""" # Quarter mapping QUARTER_NAMES = { 'q1': 'FIRST QUARTER', 'q2': 'SECOND QUARTER', 'q3': 'THIRD QUARTER', 'q4': 'FOURTH QUARTER' } # Month ranges for quarters QUARTER_MONTHS = { 'q1': 'January - March', 'q2': 'April - June', 'q3': 'July - September', 'q4': 'October - December' }
[docs] @staticmethod def get_quarter_title(config, lesson_data): """ Get the quarter title from either config or lesson data Args: config (dict): Configuration dictionary lesson_data (dict): Lesson data dictionary Returns: str: Quarter title """ # Try to get title from lesson data's front matter title = None if lesson_data and 'front_matter' in lesson_data: title_match = re.search(r'(?:^|\n)#\s+(.*?)(?:\n|$)', lesson_data['front_matter']) if title_match: title = title_match.group(1).strip() # If not found, use a default based on the first lesson title if not title and lesson_data and 'lessons' in lesson_data: first_lesson = None for week_id in sorted(lesson_data['lessons'].keys()): first_lesson = lesson_data['lessons'][week_id] break if first_lesson and 'title' in first_lesson: title = f"Topical Studies on {first_lesson['title']}" # If still not found, use default if not title: title = "Sabbath School Lessons" return title
[docs] @staticmethod def update_svg_with_config(svg_path, config, lesson_data=None, is_temporary=False): """ Update SVG file with configuration data Args: svg_path (str): Path to SVG file config (dict): Configuration dictionary lesson_data (dict, optional): Optional lesson data dictionary is_temporary (bool): Whether this is a temporary file Returns: str: Path to updated SVG file """ return if not svg_path or not os.path.exists(svg_path): return None try: with open(svg_path, 'r', encoding='utf-8') as f: svg_content = f.read() # Get quarter information - use the target year/quarter for display year = config['year'] quarter = config['quarter'] quarter_name = SvgUpdater.QUARTER_NAMES.get(quarter, 'QUARTER') month_range = SvgUpdater.QUARTER_MONTHS.get(quarter, '') # Get title title = SvgUpdater.get_quarter_title(config, lesson_data) # Check if this is a reproduction is_reproduction = 'reproduce' in config and config['reproduce'].get('year') # If reproduction, append source info to title if is_reproduction: source_year = config['reproduce']['year'] source_quarter = config['reproduce']['quarter'] source_quarter_name = SvgUpdater.QUARTER_NAMES.get(source_quarter, 'QUARTER') # Only append if not already customized if not config.get("title"): title += f" (from {source_year} {source_quarter_name})" # Replace relevant text in SVG # Update quarter info svg_content = re.sub( r'<text[^>]*>\s*(?:FIRST|SECOND|THIRD|FOURTH)\s+QUARTER,\s*\d{4}\s*</text>', f'<text x="400" y="810" font-family="Georgia, serif" font-size="22" font-weight="bold" text-anchor="middle" fill="#7d2b2b">{quarter_name}, {year}</text>', svg_content ) # Update month range svg_content = re.sub( r'<text[^>]*>\s*(?:January|April|July|October)\s+-\s+(?:March|June|September|December)\s+\d{4}\s*</text>', f'<text x="400" y="840" font-family="Georgia, serif" font-size="18" text-anchor="middle" fill="#7d2b2b">{month_range} {year}</text>', svg_content ) # Update title if it exists in SVG if title: # For "TOPICAL STUDIES" or similar main title lines svg_content = re.sub( r'<text[^>]*>TOPICAL STUDIES</text>', f'<text x="400" y="170" font-family="Georgia, serif" font-size="48" font-weight="bold" text-anchor="middle" fill="#7d2b2b">{title.upper()}</text>', svg_content ) # For "ON THE MESSAGE" or similar subtitle lines # Only if title is longer than one line, split it title_parts = title.split(' ', 3) if len(title_parts) > 3: first_line = ' '.join(title_parts[:3]) second_line = ' '.join(title_parts[3:]) # Replace first line svg_content = re.sub( r'<text[^>]*>TOPICAL STUDIES</text>', f'<text x="400" y="170" font-family="Georgia, serif" font-size="48" font-weight="bold" text-anchor="middle" fill="#7d2b2b">{first_line.upper()}</text>', svg_content ) # Replace second line svg_content = re.sub( r'<text[^>]*>ON THE MESSAGE</text>', f'<text x="400" y="230" font-family="Georgia, serif" font-size="48" font-weight="bold" text-anchor="middle" fill="#7d2b2b">{second_line.upper()}</text>', svg_content ) # Add source attribution if this is a reproduction if is_reproduction: source_year = config['reproduce']['year'] source_quarter = config['reproduce']['quarter'] source_quarter_name = SvgUpdater.QUARTER_NAMES.get(source_quarter, 'QUARTER') # Check if there's already a source attribution attribution_pattern = r'<text[^>]*>\s*Adapted from.*\s*</text>' attribution_text = f'<text x="400" y="870" font-family="Georgia, serif" font-size="16" text-anchor="middle" font-style="italic" fill="#666666">Adapted from {source_quarter_name}, {source_year}</text>' if re.search(attribution_pattern, svg_content): # Replace existing attribution svg_content = re.sub(attribution_pattern, attribution_text, svg_content) else: # Add new attribution before closing svg tag svg_content = svg_content.replace('</svg>', f'{attribution_text}\n</svg>') # Write to a new file if temporary, otherwise update in place if is_temporary: temp_svg = tempfile.NamedTemporaryFile(suffix='.svg', delete=False) temp_svg_path = temp_svg.name with open(temp_svg_path, 'w', encoding='utf-8') as f: f.write(svg_content) return temp_svg_path else: with open(svg_path, 'w', encoding='utf-8') as f: f.write(svg_content) return svg_path except Exception as e: print(f"Error updating SVG {svg_path}: {e}") return None