1
0
Files
PPTX/loaders/yaml_loader.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

163 lines
5.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.
"""
YAML 加载和验证模块
负责加载 YAML 文件并验证其结构。
"""
from pathlib import Path
import yaml
# ============= YAML 解析和验证 =============
class YAMLError(Exception):
"""YAML 相关错误"""
pass
def load_yaml_file(file_path):
"""
加载 YAML 文件UTF-8 编码,错误处理)
Args:
file_path: 文件路径(字符串或 Path 对象)
Returns:
解析后的 Python 字典
Raises:
YAMLError: 文件不存在、权限不足、YAML 语法错误等
"""
file_path = Path(file_path)
# 检查文件是否存在
if not file_path.exists():
raise YAMLError(f"文件不存在: {file_path}")
# 检查是否有读取权限
if not file_path.is_file():
raise YAMLError(f"不是有效的文件: {file_path}")
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
return data
except PermissionError:
raise YAMLError(f"权限不足,无法读取文件: {file_path}")
except yaml.YAMLError as e:
# 提取行号信息
if hasattr(e, 'problem_mark'):
mark = e.problem_mark
raise YAMLError(
f"YAML 语法错误: {file_path}, 第 {mark.line + 1} 行: {e.problem}"
)
else:
raise YAMLError(f"YAML 解析错误: {file_path}: {str(e)}")
except Exception as e:
raise YAMLError(f"读取文件失败: {file_path}: {str(e)}")
def validate_presentation_yaml(data, file_path=""):
"""
验证演示文稿 YAML 结构必需字段slides
Args:
data: 解析后的 YAML 数据
file_path: 文件路径(用于错误消息)
Raises:
YAMLError: 结构验证失败
"""
if not isinstance(data, dict):
raise YAMLError(f"{file_path}: 演示文稿必须是一个字典对象")
# 验证 slides 字段
if 'slides' not in data:
raise YAMLError(f"{file_path}: 缺少必需字段 'slides'")
if not isinstance(data['slides'], list):
raise YAMLError(f"{file_path}: 'slides' 必须是一个列表")
# 验证 templates 字段(内联模板)
validate_templates_yaml(data, file_path)
def validate_template_yaml(data, file_path=""):
"""
验证模板 YAML 结构vars, elements
Args:
data: 解析后的 YAML 数据
file_path: 文件路径(用于错误消息)
Raises:
YAMLError: 结构验证失败
"""
if not isinstance(data, dict):
raise YAMLError(f"{file_path}: 模板必须是一个字典对象")
# 验证 vars 字段
if 'vars' in data:
if not isinstance(data['vars'], list):
raise YAMLError(f"{file_path}: 'vars' 必须是一个列表")
# 验证每个变量定义
for i, var_def in enumerate(data['vars']):
if not isinstance(var_def, dict):
raise YAMLError(f"{file_path}: vars[{i}] 必须是一个字典对象")
if 'name' not in var_def:
raise YAMLError(f"{file_path}: vars[{i}] 缺少必需字段 'name'")
# 验证 elements 字段
if 'elements' not in data:
raise YAMLError(f"{file_path}: 缺少必需字段 'elements'")
if not isinstance(data['elements'], list):
raise YAMLError(f"{file_path}: 'elements' 必须是一个列表")
def validate_templates_yaml(data, file_path=""):
"""
验证 templates 字段结构(内联模板)
Args:
data: 解析后的 YAML 数据
file_path: 文件路径(用于错误消息)
Raises:
YAMLError: 结构验证失败
"""
# 验证 templates 字段是否为字典
if 'templates' in data:
if not isinstance(data['templates'], dict):
raise YAMLError(f"{file_path}: 'templates' 必须是一个字典")
# 验证每个内联模板的结构
for template_name, template_data in data['templates'].items():
# 构建模板位置路径
template_location = f"{file_path}.templates.{template_name}"
# 验证模板是字典
if not isinstance(template_data, dict):
raise YAMLError(f"{template_location}: 模板定义必须是字典")
# 验证必需的 elements 字段
if 'elements' not in template_data:
raise YAMLError(f"{template_location}: 缺少必需字段 'elements'")
if not isinstance(template_data['elements'], list):
raise YAMLError(f"{template_location}: 'elements' 必须是一个列表")
# 验证可选的 vars 字段
if 'vars' in template_data:
if not isinstance(template_data['vars'], list):
raise YAMLError(f"{template_location}: 'vars' 必须是一个列表")
# 验证每个变量定义
for i, var_def in enumerate(template_data['vars']):
if not isinstance(var_def, dict):
raise YAMLError(f"{template_location}.vars[{i}]: 变量定义必须是字典")
# 验证必需的 name 字段
if 'name' not in var_def:
raise YAMLError(f"{template_location}.vars[{i}]: 缺少必需字段 'name'")