1
0
Files
PPTX/openspec/changes/archive/2026-03-02-add-yaml-validation/design.md
lanyuanxiaoyao 83ff827ad1 feat: add YAML validation with check command and auto-validation
Implements comprehensive validation before PPTX conversion to catch errors early. Includes element-level validation (colors, fonts, table consistency) and system-level validation (geometry, resources). Supports standalone check command and automatic validation during conversion.
2026-03-02 18:14:45 +08:00

9.2 KiB
Raw Blame History

Context

当前 yaml2pptx 系统已经有基本的 YAML 加载和元素验证功能:

  • loaders/yaml_loader.py 提供基本的 YAML 语法检查和结构验证
  • core/elements.py 定义了元素数据类,在 __post_init__ 中进行基本验证

但现有验证不够全面,用户经常在转换后才发现问题(元素超出页面、文件不存在等),需要反复修改和转换。

项目约束:

  • 使用 uv 运行 Python 脚本
  • 无新增外部依赖
  • 测试文件放在 temp 目录
  • 面向中文开发者

Goals / Non-Goals

Goals:

  • 提供全面的 YAML 验证功能,在转换前发现问题
  • 支持独立 check 命令和转换前自动检查
  • 实现分级错误报告ERROR/WARNING/INFO
  • 保持验证逻辑的可扩展性和可维护性

Non-Goals:

  • 不实现自动修复功能(只检测,不修改)
  • 不支持第三阶段的质量警告(元素重叠、文本溢出等)
  • 不添加 GUI 或 Web 界面
  • 不实现配置文件来自定义验证规则

Decisions

决策 1验证职责分层

决策:将验证逻辑分为两层:

  1. 元素级验证:放在元素类本身(core/elements.py
  2. 系统级验证:放在独立的验证器模块(validators/

理由

  • 元素类最了解自己的约束,应该负责自身的完整性验证
  • 系统级验证需要全局上下文(如页面尺寸、文件路径),适合集中处理
  • 符合单一职责原则,便于扩展和维护

元素级验证职责

  • 必需字段检查(如 image 必须有 src
  • 数据类型检查(如 box 必须是 4 个数字)
  • 值的有效性检查(如颜色格式、枚举值)
  • 元素内部逻辑一致性

系统级验证职责

  • 几何验证(元素是否在页面范围内,需要知道页面尺寸)
  • 资源验证(文件是否存在,需要知道文件路径)
  • 跨元素验证(如果未来需要)

替代方案

  • 方案 A所有验证集中在验证器中 → 验证器会变得臃肿,元素类失去封装性
  • 方案 B所有验证都在元素类中 → 元素类需要知道全局上下文,违反依赖倒置原则

决策 2验证器模块结构

决策:创建 validators/ 目录,包含以下模块:

validators/
├── __init__.py          # 导出主验证器
├── validator.py         # 主验证器,协调各子验证器
├── geometry.py          # 几何验证器
├── resource.py          # 资源验证器
└── result.py            # 验证结果数据结构

理由

  • 模块化设计,每个验证器职责单一
  • 便于测试和扩展
  • 主验证器作为门面,简化调用

验证流程

Validator.validate(yaml_path, template_dir)
  ↓
1. 加载 YAML复用 yaml_loader
2. 元素级验证(调用元素类的验证方法)
3. 几何验证GeometryValidator
4. 资源验证ResourceValidator
  ↓
返回 ValidationResult

替代方案

  • 方案 A单一验证器文件 → 代码过长,难以维护
  • 方案 B每种验证一个独立包 → 过度设计,增加复杂度

决策 3验证结果数据结构

决策:定义 ValidationResultValidationIssue 类:

@dataclass
class ValidationIssue:
    level: str  # "ERROR" | "WARNING" | "INFO"
    message: str
    location: str  # "幻灯片 2, 元素 3"
    code: str  # "ELEMENT_OUT_OF_BOUNDS"

@dataclass
class ValidationResult:
    valid: bool  # 是否有 ERROR
    errors: List[ValidationIssue]
    warnings: List[ValidationIssue]
    infos: List[ValidationIssue]

    def has_errors(self) -> bool
    def format_output(self) -> str  # 格式化为命令行输出

理由

  • 结构化的结果便于测试和扩展
  • code 字段便于未来实现错误码查询或国际化
  • format_output() 方法封装输出格式,便于修改

替代方案

  • 方案 A直接返回字符串 → 难以测试,不便于扩展
  • 方案 B返回字典 → 缺少类型安全,容易出错

决策 4命令行接口设计

决策:修改 yaml2pptx.py,使用子命令模式:

# 独立验证
yaml2pptx.py check input.yaml [--template-dir DIR]

# 转换(默认自动验证)
yaml2pptx.py input.yaml [output.pptx] [--template-dir DIR] [--no-check]

实现方式

  • 使用 argparsesubparsers 实现子命令
  • check 子命令调用验证器并输出结果
  • 转换命令在加载 YAML 后、渲染前调用验证器
  • 如果验证失败(有 ERROR终止转换并返回非零退出码

理由

  • 子命令模式清晰,符合 CLI 工具惯例
  • 默认启用自动验证,提升用户体验
  • --no-check 提供灵活性,适合调试场景

替代方案

  • 方案 A独立的 yaml2pptx-check 命令 → 增加维护成本,用户需要记住两个命令
  • 方案 B只有自动验证没有独立命令 → 用户无法单独验证

决策 5元素类验证方法增强

决策:在 core/elements.py 中为每个元素类添加 validate() 方法:

@dataclass
class TextElement:
    # ... 现有字段 ...

    def validate(self) -> List[ValidationIssue]:
        """验证元素自身的完整性"""
        issues = []

        # 检查颜色格式
        if self.font.get('color'):
            if not self._is_valid_color(self.font['color']):
                issues.append(ValidationIssue(
                    level="ERROR",
                    message=f"无效的颜色格式: {self.font['color']}",
                    location="",  # 由调用者填充
                    code="INVALID_COLOR_FORMAT"
                ))

        # 检查字体大小
        if self.font.get('size'):
            size = self.font['size']
            if size < 8:
                issues.append(ValidationIssue(
                    level="WARNING",
                    message=f"字体太小: {size}pt (建议 >= 8pt)",
                    location="",
                    code="FONT_TOO_SMALL"
                ))

        return issues

理由

  • 元素类封装自己的验证逻辑
  • __post_init__ 保留用于阻止创建无效对象(如 box 不是 4 个数字)
  • validate() 用于检测可以创建但不推荐的情况(如字体太小)

替代方案

  • 方案 A只在 __post_init__ 中验证 → 无法区分致命错误和警告
  • 方案 B不修改元素类所有验证在验证器中 → 违反封装原则

决策 6边界检查容忍度

决策:几何验证时,允许 0.1 英寸的容忍度:

TOLERANCE = 0.1  # 英寸

if right > slide_width + TOLERANCE:
    # 报告 WARNING

理由

  • 浮点数计算可能有精度误差
  • 0.1 英寸(约 2.54mm)在视觉上几乎不可见
  • 避免误报,提升用户体验

替代方案

  • 方案 A零容忍 → 可能产生大量误报
  • 方案 B更大的容忍度如 0.5 英寸)→ 可能漏掉真正的问题

Risks / Trade-offs

风险 1验证性能影响

风险:验证可能增加转换时间,特别是资源验证(检查文件存在性)。

缓解措施

  • 验证是可选的(--no-check 跳过)
  • 资源验证只检查文件存在性,不读取文件内容
  • 未来可以考虑并行验证(如果性能成为问题)

风险 2元素类验证方法的维护成本

风险:每个元素类都需要实现 validate() 方法,增加维护成本。

缓解措施

  • 提供基类或工具函数来复用常见验证逻辑(如颜色格式检查)
  • 验证逻辑相对稳定,不会频繁修改
  • 好处是验证逻辑和元素定义在一起,便于理解和修改

风险 3错误消息的可读性

风险:错误消息可能不够清晰,用户难以理解如何修复。

缓解措施

  • 错误消息包含具体的位置信息(幻灯片、元素)
  • 错误消息包含期望值和实际值(如 "right=10.5 > 10.0"
  • 未来可以添加错误码和文档链接

Trade-off验证完整性 vs 性能

选择:优先保证验证完整性,性能其次。

理由

  • 验证是可选的,性能敏感的场景可以跳过
  • 提前发现问题比快速转换更重要
  • 当前验证项不多,性能影响可控

Migration Plan

部署步骤

  1. 实现验证器模块(不影响现有功能)
  2. 增强元素类的验证方法(向后兼容)
  3. 修改 yaml2pptx.py 添加 check 子命令
  4. 添加自动验证逻辑(默认启用)
  5. 编写测试用例(在 temp 目录)
  6. 更新 README.md 文档

回滚策略

  • 如果验证器有问题,用户可以使用 --no-check 跳过
  • 验证器是独立模块,可以快速禁用或修复
  • 不影响现有的转换功能

兼容性

  • 完全向后兼容,现有 YAML 文件和命令行用法不受影响
  • 新增的 check 子命令和 --no-check 选项是可选的

Open Questions

  1. 是否需要配置文件来自定义验证规则?

    • 当前决策:不需要,保持简单
    • 未来可以考虑添加 .yaml2pptx.yaml 配置文件
  2. 是否需要支持 JSON 格式的验证结果输出?

    • 当前决策:只支持命令行文本输出
    • 未来可以添加 --format json 选项
  3. 字体大小的合理范围是否需要可配置?

    • 当前决策:硬编码 8pt-100pt
    • 未来可以考虑通过配置文件自定义