""" Base Boilerplate Model ====================== This module provides a base class for all L5X models that use boilerplate templates. """ import xml.etree.ElementTree as ET from dataclasses import dataclass from typing import Optional, Dict import os from datetime import datetime from abc import ABC, abstractmethod @dataclass class BaseModuleConfig(ABC): """Base configuration for all modules.""" name: str @abstractmethod def get_updates(self) -> Dict[str, any]: """Get dictionary of updates to apply to the boilerplate.""" pass class BaseBoilerplateGenerator(ABC): """Base generator for L5X files using boilerplate templates.""" def __init__(self, config: BaseModuleConfig): """Initialize with configuration.""" self.config = config self.tree = None self.root = None def load_boilerplate(self): """Load the boilerplate L5X file.""" if not hasattr(self.config, 'boilerplate_path'): raise AttributeError("Config must have a boilerplate_path attribute") if not os.path.exists(self.config.boilerplate_path): raise FileNotFoundError(f"Boilerplate file not found: {self.config.boilerplate_path}") self.tree = ET.parse(self.config.boilerplate_path) self.root = self.tree.getroot() def update_export_date(self): """Update the export date to current time.""" export_date = datetime.now().strftime("%a %b %d %H:%M:%S %Y") self.root.set("ExportDate", export_date) @abstractmethod def apply_updates(self): """Apply module-specific updates to the boilerplate.""" pass def generate(self) -> str: """Generate the complete L5X file as a string.""" # Load boilerplate self.load_boilerplate() # Apply updates self.apply_updates() self.update_export_date() # Convert to string with proper formatting return self._to_pretty_xml() def _to_pretty_xml(self) -> str: """Convert the XML tree to a pretty-printed string.""" # Create XML declaration xml_str = '\n' # Add the root element self._indent(self.root) xml_str += ET.tostring(self.root, encoding='unicode') return xml_str def _indent(self, elem, level=0): """Add proper indentation to XML elements.""" i = "\n" + level * " " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " if not elem.tail or not elem.tail.strip(): elem.tail = i for child in elem: self._indent(child, level + 1) if not child.tail or not child.tail.strip(): child.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i def save_to_file(self, filename: str): """Generate and save the L5X content to a file.""" content = self.generate() with open(filename, 'w', encoding='utf-8') as f: f.write(content)