## 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:验证结果数据结构 **决策**:定义 `ValidationResult` 和 `ValidationIssue` 类: ```python @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`,使用子命令模式: ```python # 独立验证 yaml2pptx.py check input.yaml [--template-dir DIR] # 转换(默认自动验证) yaml2pptx.py input.yaml [output.pptx] [--template-dir DIR] [--no-check] ``` **实现方式**: - 使用 `argparse` 的 `subparsers` 实现子命令 - `check` 子命令调用验证器并输出结果 - 转换命令在加载 YAML 后、渲染前调用验证器 - 如果验证失败(有 ERROR),终止转换并返回非零退出码 **理由**: - 子命令模式清晰,符合 CLI 工具惯例 - 默认启用自动验证,提升用户体验 - `--no-check` 提供灵活性,适合调试场景 **替代方案**: - 方案 A:独立的 `yaml2pptx-check` 命令 → 增加维护成本,用户需要记住两个命令 - 方案 B:只有自动验证,没有独立命令 → 用户无法单独验证 ### 决策 5:元素类验证方法增强 **决策**:在 `core/elements.py` 中为每个元素类添加 `validate()` 方法: ```python @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 英寸的容忍度: ```python 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 - 未来可以考虑通过配置文件自定义