支持在 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 字段时行为不变。
166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
"""
|
||
主验证器
|
||
|
||
协调各子验证器,执行完整的 YAML 文件验证。
|
||
"""
|
||
|
||
from pathlib import Path
|
||
from loaders.yaml_loader import load_yaml_file, validate_presentation_yaml, YAMLError
|
||
from validators.result import ValidationResult, ValidationIssue
|
||
from validators.geometry import GeometryValidator
|
||
from validators.resource import ResourceValidator
|
||
from core.elements import create_element
|
||
|
||
|
||
class Validator:
|
||
"""主验证器"""
|
||
|
||
# 幻灯片尺寸映射(英寸)
|
||
SLIDE_SIZES = {
|
||
"16:9": (10, 5.625),
|
||
"4:3": (10, 7.5),
|
||
}
|
||
|
||
def __init__(self):
|
||
"""初始化验证器"""
|
||
pass
|
||
|
||
def validate(self, yaml_path: Path, template_dir: Path = None) -> ValidationResult:
|
||
"""
|
||
验证 YAML 文件
|
||
|
||
Args:
|
||
yaml_path: YAML 文件路径
|
||
template_dir: 模板文件目录(可选)
|
||
|
||
Returns:
|
||
验证结果
|
||
"""
|
||
errors = []
|
||
warnings = []
|
||
infos = []
|
||
|
||
# 1. 加载 YAML
|
||
try:
|
||
data = load_yaml_file(yaml_path)
|
||
validate_presentation_yaml(data, str(yaml_path))
|
||
except YAMLError as e:
|
||
errors.append(
|
||
ValidationIssue(
|
||
level="ERROR", message=str(e), location="", code="YAML_ERROR"
|
||
)
|
||
)
|
||
return ValidationResult(
|
||
valid=False, errors=errors, warnings=warnings, infos=infos
|
||
)
|
||
except Exception as e:
|
||
errors.append(
|
||
ValidationIssue(
|
||
level="ERROR",
|
||
message=f"加载 YAML 文件失败: {str(e)}",
|
||
location="",
|
||
code="YAML_LOAD_ERROR",
|
||
)
|
||
)
|
||
return ValidationResult(
|
||
valid=False, errors=errors, warnings=warnings, infos=infos
|
||
)
|
||
|
||
# 获取幻灯片尺寸
|
||
size_str = data.get("metadata", {}).get("size", "16:9")
|
||
slide_width, slide_height = self.SLIDE_SIZES.get(size_str, (10, 5.625))
|
||
|
||
# 初始化子验证器
|
||
geometry_validator = GeometryValidator(slide_width, slide_height)
|
||
resource_validator = ResourceValidator(
|
||
yaml_dir=yaml_path.parent, template_dir=template_dir, yaml_data=data
|
||
)
|
||
|
||
# 2. 验证每个幻灯片
|
||
slides = data.get("slides", [])
|
||
for slide_index, slide_data in enumerate(slides, start=1):
|
||
# 验证模板
|
||
template_issues = resource_validator.validate_template(
|
||
slide_data, slide_index
|
||
)
|
||
self._categorize_issues(template_issues, errors, warnings, infos)
|
||
|
||
# 验证模板变量
|
||
template_var_issues = resource_validator.validate_template_vars(
|
||
slide_data, slide_index
|
||
)
|
||
self._categorize_issues(template_var_issues, errors, warnings, infos)
|
||
|
||
# 验证元素
|
||
elements = slide_data.get("elements", [])
|
||
for elem_index, elem_dict in enumerate(elements, start=1):
|
||
# 3. 元素级验证
|
||
try:
|
||
element = create_element(elem_dict)
|
||
|
||
# 调用元素的 validate() 方法
|
||
if hasattr(element, "validate"):
|
||
elem_issues = element.validate()
|
||
# 填充位置信息
|
||
for issue in elem_issues:
|
||
issue.location = f"幻灯片 {slide_index}, 元素 {elem_index}"
|
||
self._categorize_issues(elem_issues, errors, warnings, infos)
|
||
|
||
# 4. 几何验证
|
||
geom_issues = geometry_validator.validate_element(
|
||
element, slide_index, elem_index
|
||
)
|
||
self._categorize_issues(geom_issues, errors, warnings, infos)
|
||
|
||
# 5. 资源验证(图片)
|
||
if elem_dict.get("type") == "image":
|
||
img_issues = resource_validator.validate_image(
|
||
element, slide_index, elem_index
|
||
)
|
||
self._categorize_issues(img_issues, errors, warnings, infos)
|
||
|
||
except ValueError as e:
|
||
# 元素创建失败(__post_init__ 中的验证)
|
||
errors.append(
|
||
ValidationIssue(
|
||
level="ERROR",
|
||
message=str(e),
|
||
location=f"幻灯片 {slide_index}, 元素 {elem_index}",
|
||
code="ELEMENT_VALIDATION_ERROR",
|
||
)
|
||
)
|
||
except Exception as e:
|
||
errors.append(
|
||
ValidationIssue(
|
||
level="ERROR",
|
||
message=f"验证元素时出错: {str(e)}",
|
||
location=f"幻灯片 {slide_index}, 元素 {elem_index}",
|
||
code="ELEMENT_VALIDATION_ERROR",
|
||
)
|
||
)
|
||
|
||
# 返回验证结果
|
||
return ValidationResult(
|
||
valid=len(errors) == 0, errors=errors, warnings=warnings, infos=infos
|
||
)
|
||
|
||
def _categorize_issues(
|
||
self, issues: list, errors: list, warnings: list, infos: list
|
||
):
|
||
"""
|
||
将问题按级别分类
|
||
|
||
Args:
|
||
issues: 问题列表
|
||
errors: 错误列表
|
||
warnings: 警告列表
|
||
infos: 提示列表
|
||
"""
|
||
for issue in issues:
|
||
if issue.level == "ERROR":
|
||
errors.append(issue)
|
||
elif issue.level == "WARNING":
|
||
warnings.append(issue)
|
||
elif issue.level == "INFO":
|
||
infos.append(issue)
|