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 字段时行为不变。
This commit is contained in:
@@ -88,8 +88,12 @@ yaml2pptx.py (入口)
|
||||
- **包含**:
|
||||
- `YAMLError` - 自定义异常
|
||||
- `load_yaml_file()` - 加载 YAML 文件
|
||||
- `validate_presentation_yaml()` - 验证演示文稿结构
|
||||
- `validate_template_yaml()` - 验证模板结构
|
||||
- `validate_presentation_yaml()` - 验证演示文稿结构,调用 `validate_templates_yaml()` 验证内联模板
|
||||
- `validate_template_yaml()` - 验证外部模板结构
|
||||
- `validate_templates_yaml()` - 验证内联模板结构(templates 字段)
|
||||
- **特点**:
|
||||
- 内联模板验证包括:结构验证、元素验证、变量定义验证、默认值验证
|
||||
- 检测默认值中引用不存在的变量
|
||||
|
||||
### 4. core/elements.py(核心层 - 元素抽象)
|
||||
- **职责**:定义元素数据类和工厂函数
|
||||
@@ -130,19 +134,29 @@ yaml2pptx.py (入口)
|
||||
- **职责**:模板加载和变量解析
|
||||
- **包含**:
|
||||
- `Template` 类
|
||||
- `from_data()` 类方法:从字典创建模板实例(用于内联模板)
|
||||
- 变量解析:`resolve_value()`, `resolve_element()`
|
||||
- 条件渲染:`evaluate_condition()`
|
||||
- 模板渲染:`render()`
|
||||
- **特点**:
|
||||
- 支持外部模板(从文件加载)和内联模板(从字典创建)
|
||||
- 内联模板通过 `from_data()` 类方法创建,避免修改现有 `__init__`
|
||||
- 禁止内联模板相互引用,在渲染时检测并报错
|
||||
|
||||
### 6. core/presentation.py(核心层 - 演示文稿)
|
||||
- **职责**:演示文稿管理和幻灯片渲染
|
||||
- **包含**:
|
||||
- `Presentation` 类
|
||||
- 模板缓存:`get_template()`
|
||||
- 内联模板存储:`__init__` 中解析并保存 `templates` 字段到 `self.inline_templates`
|
||||
- 模板查找:`get_template()` 优先查找内联模板,然后回退到外部模板
|
||||
- 同名检测:`_external_template_exists()` 检查外部模板是否存在,防止命名冲突
|
||||
- 模板缓存:外部模板使用 `template_cache` 缓存
|
||||
- 幻灯片渲染:`render_slide()`
|
||||
- **特点**:
|
||||
- 将元素字典转换为元素对象
|
||||
- 使用 `create_element()` 工厂函数
|
||||
- 内联和外部模板同名时抛出 ERROR 错误
|
||||
- 内联模板不需要缓存(已在内存中)
|
||||
|
||||
### 7. renderers/pptx_renderer.py(渲染层 - PPTX)
|
||||
- **职责**:PPTX 文件生成
|
||||
@@ -379,6 +393,66 @@ if right > slide_width + TOLERANCE:
|
||||
# 报告 WARNING
|
||||
```
|
||||
|
||||
### 7. 内联模板系统
|
||||
|
||||
**决策**:支持在 YAML 源文件中定义内联模板,与外部模板系统共存
|
||||
|
||||
**理由**:
|
||||
- 降低使用门槛:简单场景无需创建单独的模板文件
|
||||
- 保持灵活性:复杂场景仍可使用外部模板
|
||||
- 向后兼容:不影响现有外部模板功能
|
||||
|
||||
**实现要点**:
|
||||
|
||||
1. **模板定义**:在 YAML 顶层添加 `templates` 字段
|
||||
```yaml
|
||||
templates:
|
||||
my-template:
|
||||
vars: [...]
|
||||
elements: [...]
|
||||
```
|
||||
|
||||
2. **模板创建**:使用 `Template.from_data()` 类方法
|
||||
```python
|
||||
@classmethod
|
||||
def from_data(cls, template_data, template_name):
|
||||
"""从字典创建模板(内联模板)"""
|
||||
obj = cls.__new__(cls)
|
||||
obj.data = template_data
|
||||
obj.vars_def = {var['name']: var for var in template_data.get('vars', [])}
|
||||
obj.elements = template_data.get('elements', [])
|
||||
return obj
|
||||
```
|
||||
|
||||
3. **模板查找**:`Presentation.get_template()` 优先查找内联模板
|
||||
```python
|
||||
def get_template(self, template_name):
|
||||
# 1. 检查内联模板
|
||||
if hasattr(self, 'inline_templates') and template_name in self.inline_templates:
|
||||
# 2. 检查同名冲突
|
||||
if self._external_template_exists(template_name):
|
||||
raise YAMLError(f"模板名称冲突: '{template_name}'")
|
||||
return Template.from_data(self.inline_templates[template_name], template_name)
|
||||
# 3. 回退到外部模板
|
||||
return self._get_external_template(template_name)
|
||||
```
|
||||
|
||||
4. **同名检测**:禁止内联和外部模板同名
|
||||
- 显式报错比隐式选择更安全
|
||||
- 强制用户明确模板来源
|
||||
- 降低调试难度
|
||||
|
||||
5. **限制**:禁止内联模板相互引用
|
||||
- 降低实现复杂度
|
||||
- 内联模板适合简单场景
|
||||
- 复杂引用应使用外部模板
|
||||
|
||||
6. **验证**:`validate_templates_yaml()` 验证内联模板结构
|
||||
- 检查 `templates` 是否为字典
|
||||
- 检查每个模板是否有必需的 `elements` 字段
|
||||
- 检查变量定义是否有必需的 `name` 字段
|
||||
- 检测默认值中引用不存在的变量
|
||||
|
||||
## 扩展指南
|
||||
|
||||
### 添加新元素类型
|
||||
|
||||
Reference in New Issue
Block a user