""" 模板系统模块 管理可复用的幻灯片布局和变量解析。 """ import re from pathlib import Path from loaders.yaml_loader import YAMLError, load_yaml_file, validate_template_yaml from core.condition_evaluator import ConditionEvaluator class Template: """模板类,管理可复用的幻灯片布局""" def __init__(self, template_file, templates_dir=None): """ 初始化模板 Args: template_file: 模板名称(纯文件名,不含路径) templates_dir: 模板文件目录 """ # 初始化条件评估器 self._condition_evaluator = ConditionEvaluator() # 检查是否提供了 templates_dir if templates_dir is None: raise YAMLError( f"未指定模板目录,无法加载模板: {template_file}\n" f"请使用 --template-dir 参数指定模板目录" ) # 验证模板名称(不能包含路径分隔符) if '/' in template_file or '\\' in template_file: raise YAMLError( f"模板名称不能包含路径分隔符: {template_file}\n" f"模板名称应该是纯文件名,如: 'title-slide'" ) # 构建模板路径 template_path = Path(templates_dir) / f"{template_file}.yaml" # 检查文件是否存在 if not template_path.exists(): raise YAMLError( f"模板文件不存在: {template_file}\n" f"查找位置: {templates_dir}\n" f"期望文件: {template_path}\n" f"提示: 请检查模板名称和模板目录是否正确" ) # 加载并验证模板文件 self.data = load_yaml_file(template_path) validate_template_yaml(self.data, str(template_path)) # 可选的描述字段 self.description = self.data.get('description') # 解析变量定义 self.vars_def = {} for var in self.data.get('vars', []): self.vars_def[var['name']] = var # 元素列表 self.elements = self.data.get('elements', []) @classmethod def from_data(cls, template_data, template_name): """从字典创建模板(内联模板) Args: template_data: 模板数据字典 template_name: 模板名称 Returns: Template 对象 """ obj = cls.__new__(cls) obj.data = template_data # 初始化条件评估器 obj._condition_evaluator = ConditionEvaluator() # 可选的描述字段 obj.description = template_data.get('description') # 解析变量定义 obj.vars_def = {} for var in template_data.get('vars', []): obj.vars_def[var['name']] = var # 元素列表 obj.elements = template_data.get('elements', []) return obj def _external_template_exists(self, template_name): """检查外部模板文件是否存在""" if not hasattr(self, 'templates_dir') or not self.templates_dir: return False template_path = Path(self.templates_dir) / f"{template_name}.yaml" return template_path.exists() def resolve_value(self, value, vars_values): """ 解析单个值中的变量引用 Args: value: 要解析的值 vars_values: 用户提供的变量值字典 Returns: 解析后的值 """ if not isinstance(value, str): return value # 匹配 {xxx} 模式 pattern = r'\{([^}]+)\}' def replacer(match): expr = match.group(1) # 模板变量: {title} if expr in vars_values: return str(vars_values[expr]) else: raise YAMLError(f"未定义的变量: {expr}") result = re.sub(pattern, replacer, value) # 如果结果是纯数字字符串,转换回数字类型 try: # 尝试转换为整数 if '.' not in result: return int(result) # 尝试转换为浮点数 else: return float(result) except ValueError: # 不是数字,返回字符串 return result def resolve_element(self, elem, vars_values): """ 递归解析元素中的所有变量 Args: elem: 元素(dict, list, 或其他类型) vars_values: 用户提供的变量值字典 Returns: 解析后的元素 """ if isinstance(elem, dict): return {k: self.resolve_element(v, vars_values) for k, v in elem.items() if k != 'visible'} elif isinstance(elem, list): return [self.resolve_element(item, vars_values) for item in elem] elif isinstance(elem, str): return self.resolve_value(elem, vars_values) else: return elem def evaluate_condition(self, condition, vars_values): """ 评估条件表达式 Args: condition: 条件字符串,如 "{count > 0 and status == 'active'}" vars_values: 变量值字典 Returns: bool: 条件是否满足 """ # 委托给条件评估器 return self._condition_evaluator.evaluate_condition(condition, vars_values) def render(self, vars_values): """ 渲染模板,返回实际的元素列表 Args: vars_values: 用户提供的变量值字典 Returns: list: 渲染后的元素列表 Raises: YAMLError: 缺少必需变量,内联模板相互引用 """ # 检测内联模板相互引用(禁止) for elem in self.elements: if isinstance(elem, dict) and 'template' in elem: raise YAMLError( f"内联模板不支持相互引用:元素中包含 'template' 字段\n" f"提示: 内联模板只能包含元素定义,不能引用其他模板" ) # 填充所有变量的默认值(如果用户未提供) for var_name, var_def in self.vars_def.items(): if var_name not in vars_values: # 检查是否是必需变量 if var_def.get('required', False): # 必需变量必须有默认值或用户提供 if 'default' in var_def: vars_values[var_name] = self.resolve_value( var_def['default'], vars_values ) else: raise YAMLError(f"缺少必需变量: {var_name}") else: # 可选变量使用默认值(如果有) if 'default' in var_def: vars_values[var_name] = self.resolve_value( var_def['default'], vars_values ) # 渲染所有元素 rendered_elements = [] for elem in self.elements: # 检查条件渲染 if 'visible' in elem: if not self.evaluate_condition(elem['visible'], vars_values): continue # 深度解析元素中的所有变量引用 rendered_elem = self.resolve_element(elem, vars_values) rendered_elements.append(rendered_elem) return rendered_elements