1
0
Files
PPTX/core/template.py
lanyuanxiaoyao 16ca9d77cd feat: 增强模板条件渲染表达式支持
使用 simpleeval 库替换原有的简单正则匹配,支持复杂的条件表达式评估。新增 ConditionEvaluator 类处理条件逻辑,支持比较运算、逻辑运算、成员测试、数学计算和内置函数,同时保持向后兼容性。
2026-03-03 17:28:23 +08:00

228 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
模板系统模块
管理可复用的幻灯片布局和变量解析。
"""
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.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.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