1
0
Files
PPTX/core/presentation.py
lanyuanxiaoyao 01eacb0b97 feat: 添加内联模板支持
支持在 YAML 源文件中直接定义模板,无需单独的模板文件。
简化单文档编写流程,降低模板系统使用门槛。

核心功能:
- 在 YAML 顶层新增 templates 字段定义内联模板
- 支持变量替换、条件渲染、默认值等完整模板功能
- 内联模板优先于外部模板查找
- 同名冲突检测:禁止内联和外部模板同名
- 相互引用检测:禁止内联模板之间相互引用
- 完整的错误处理和验证机制

代码变更:
- core/template.py: 新增 from_data() 类方法
- core/presentation.py: 支持内联模板查找和冲突检测
- loaders/yaml_loader.py: 新增 validate_templates_yaml() 验证
- validators/: 扩展验证器支持内联模板

测试:
- 新增 9 个内联模板专项测试
- 修复 1 个变量验证测试
- 所有 333 个测试通过

文档:
- README.md: 添加内联模板使用指南和最佳实践
- README_DEV.md: 说明实现细节和设计决策

完全向后兼容,不使用 templates 字段时行为不变。
2026-03-03 15:59:55 +08:00

119 lines
3.8 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.
"""
演示文稿模块
管理整个演示文稿的生成流程。
"""
from pathlib import Path
from loaders.yaml_loader import load_yaml_file, validate_presentation_yaml, YAMLError
from core.template import Template
from core.elements import create_element
class Presentation:
"""演示文稿类,管理整个演示文稿的生成流程"""
def __init__(self, pres_file, templates_dir=None):
"""
初始化演示文稿
Args:
pres_file: 演示文稿 YAML 文件路径
templates_dir: 模板目录
"""
self.pres_file = Path(pres_file)
self.templates_dir = templates_dir
# 加载演示文稿文件
self.data = load_yaml_file(pres_file)
validate_presentation_yaml(self.data, str(pres_file))
# 获取演示文稿尺寸
metadata = self.data.get("metadata", {})
self.size = metadata.get("size", "16:9")
# 验证尺寸值
if not isinstance(self.size, str):
raise ValueError(
f"无效的尺寸值: {self.size},尺寸必须是字符串(如 '16:9''4:3'"
)
# 模板缓存
self.template_cache = {}
# 解析并保存内联模板
self.inline_templates = self.data.get('templates', {})
def get_template(self, template_name):
"""
获取模板(优先检查内联模板,检测同名冲突)
Args:
template_name: 模板名称
Returns:
Template 对象
Raises:
YAMLError: 内联和外部模板同名
"""
# 1. 先检查内联模板
if template_name in self.inline_templates:
# 2. 检查外部模板是否也存在同名
if self._external_template_exists(template_name):
raise YAMLError(
f"模板名称冲突: '{template_name}' 同时存在于内联模板和外部模板目录\n"
f"请使用不同的模板名称以避免冲突"
)
inline_data = self.inline_templates[template_name]
return Template.from_data(inline_data, template_name)
# 3. 回退到外部模板
if template_name not in self.template_cache:
self.template_cache[template_name] = Template(
template_name, self.templates_dir
)
return self.template_cache[template_name]
def _external_template_exists(self, template_name):
"""检查外部模板文件是否存在"""
if not self.templates_dir:
return False
template_path = Path(self.templates_dir) / f"{template_name}.yaml"
return template_path.exists()
def render_slide(self, slide_data):
"""
渲染单个幻灯片
Args:
slide_data: 幻灯片数据字典
Returns:
dict: 包含 background 和 elements 的字典
"""
if "template" in slide_data:
# 使用模板
template_name = slide_data["template"]
template = self.get_template(template_name)
vars_values = slide_data.get("vars", {})
elements = template.render(vars_values)
# 合并背景(如果有)
background = slide_data.get("background", None)
# 将元素字典转换为元素对象
element_objects = [create_element(elem) for elem in elements]
return {"background": background, "elements": element_objects}
else:
# 自定义幻灯片
elements = slide_data.get("elements", [])
# 将元素字典转换为元素对象
element_objects = [create_element(elem) for elem in elements]
return {
"background": slide_data.get("background"),
"elements": element_objects,
}