1
0
Files
PPTX/README_DEV.md
lanyuanxiaoyao 66472cbd86 refactor: restructure CLI with clear subcommand architecture
重构命令行接口,建立清晰的子命令架构,提升用户体验和代码可维护性。

主要变更:
- 移除传统模式,统一使用子命令架构(check/convert/preview)
- 将 preview 从 convert 的标志独立为子命令,职责分离
- 重命名参数:--no-check → --skip-validation
- 新增 --force/-f:convert 命令支持强制覆盖已存在文件
- 新增 --host:preview 命令支持配置主机地址(局域网预览)
- 新增 --no-browser:preview 命令支持不自动打开浏览器
- 优化 --port 默认值:从固定端口改为随机端口(30000-40000)

破坏性变更:
- 不再支持传统模式(yaml2pptx.py input.yaml output.pptx)
- convert 命令不再支持 --preview 参数,需使用 preview 子命令

文档更新:
- 更新 README.md 和 README_DEV.md 的所有使用示例
- 更新命令行选项说明表格
- 新增 CLI 接口规范文档

OpenSpec:
- 创建 cli-interface 规范(新能力)
- 更新 browser-preview-server 规范(修改的能力)
- 归档 refactor-cli-args change(45/45 任务完成)
2026-03-02 18:47:50 +08:00

18 KiB
Raw Blame History

开发文档

本文档说明 yaml2pptx 项目的代码结构、开发规范和技术决策。

项目概述

yaml2pptx 是一个将 YAML 格式的演示文稿源文件转换为 PPTX 文件的工具,支持模板系统和浏览器预览功能。

代码结构

项目采用模块化架构,按功能职责组织代码:

html2pptx/
├── yaml2pptx.py (200 行)          # 入口脚本CLI + main 函数
├── utils.py (74 行)               # 工具函数(日志、颜色转换)
├── core/                          # 核心领域模型
│   ├── elements.py (200 行)       # 元素抽象层dataclass + validate
│   ├── template.py (191 行)       # 模板系统
│   └── presentation.py (91 行)    # 演示文稿类
├── loaders/                       # 数据加载层
│   └── yaml_loader.py (113 行)    # YAML 加载和验证
├── validators/                    # 验证层
│   ├── __init__.py                # 导出主验证器
│   ├── result.py (70 行)          # 验证结果数据结构
│   ├── validator.py (150 行)      # 主验证器
│   ├── geometry.py (120 行)       # 几何验证器
│   └── resource.py (110 行)       # 资源验证器
├── renderers/                     # 渲染层
│   ├── pptx_renderer.py (292 行)  # PPTX 渲染器
│   └── html_renderer.py (172 行)  # HTML 渲染器(预览)
└── preview/                       # 预览功能
    └── server.py (244 行)         # Flask 服务器 + 文件监听

依赖关系

yaml2pptx.py (入口)
    ↓
    ├─→ utils (工具函数)
    ├─→ loaders.yaml_loader (YAML 加载)
    ├─→ validators.validator (验证器)
    │       ↓
    │       ├─→ validators.result (验证结果)
    │       ├─→ validators.geometry (几何验证)
    │       ├─→ validators.resource (资源验证)
    │       └─→ core.elements (元素验证)
    ├─→ core.presentation (演示文稿)
    │       ↓
    │       ├─→ core.template (模板)
    │       └─→ core.elements (元素)
    ├─→ renderers.pptx_renderer (PPTX 生成)
    │       ↓
    │       └─→ core.elements
    └─→ preview.server (预览服务)
            ↓
            └─→ renderers.html_renderer
                    ↓
                    └─→ core.elements

依赖原则

  • 单向依赖:入口 → 验证/渲染 → 核心 ← 加载
  • 无循环依赖
  • 核心层不依赖其他业务模块
  • 验证层可以调用核心层的元素验证方法

模块职责

1. yaml2pptx.py入口层

  • 职责CLI 参数解析、流程编排
  • 行数:约 100-150 行
  • 包含
    • /// script 依赖声明
    • parse_args() - 命令行参数解析
    • main() - 主流程编排
  • 不包含:业务逻辑、数据处理

2. utils.py工具层

  • 职责:通用工具函数
  • 包含
    • 日志函数:log_info(), log_success(), log_error(), log_progress()
    • 颜色转换:hex_to_rgb(), validate_color()

3. loaders/yaml_loader.py加载层

  • 职责YAML 文件加载和验证
  • 包含
    • YAMLError - 自定义异常
    • load_yaml_file() - 加载 YAML 文件
    • validate_presentation_yaml() - 验证演示文稿结构
    • validate_template_yaml() - 验证模板结构

4. core/elements.py核心层 - 元素抽象)

  • 职责:定义元素数据类和工厂函数
  • 包含
    • _is_valid_color() - 颜色格式验证工具函数
    • TextElement - 文本元素dataclass + validate
    • ImageElement - 图片元素dataclass + validate
    • ShapeElement - 形状元素dataclass + validate
    • TableElement - 表格元素dataclass + validate
    • create_element() - 元素工厂函数
  • 特点
    • 使用 @dataclass 装饰器
    • __post_init__ 中进行创建时验证
    • validate() 方法中进行元素级验证
    • 元素类负责自身属性的验证(颜色格式、字体大小、枚举值等)

4.5. validators/(验证层)

  • 职责YAML 文件验证,在转换前检查问题
  • 包含
    • validators/result.py - 验证结果数据结构
      • ValidationIssue - 验证问题level, message, location, code
      • ValidationResult - 验证结果errors, warnings, infos
    • validators/geometry.py - 几何验证器
      • GeometryValidator - 检查元素边界、页面范围
      • 支持 0.1 英寸容忍度
    • validators/resource.py - 资源验证器
      • ResourceValidator - 检查图片、模板文件存在性
      • 验证模板文件结构
    • validators/validator.py - 主验证器
      • Validator - 协调所有子验证器
      • 集成元素级验证、几何验证、资源验证
  • 特点
    • 分级错误报告ERROR/WARNING/INFO
    • ERROR 阻止转换WARNING 不阻止
    • 验证职责分层:元素级验证在元素类中,系统级验证在验证器中

5. core/template.py核心层 - 模板)

  • 职责:模板加载和变量解析
  • 包含
    • Template
    • 变量解析:resolve_value(), resolve_element()
    • 条件渲染:evaluate_condition()
    • 模板渲染:render()

6. core/presentation.py核心层 - 演示文稿)

  • 职责:演示文稿管理和幻灯片渲染
  • 包含
    • Presentation
    • 模板缓存:get_template()
    • 幻灯片渲染:render_slide()
  • 特点
    • 将元素字典转换为元素对象
    • 使用 create_element() 工厂函数

7. renderers/pptx_renderer.py渲染层 - PPTX

  • 职责PPTX 文件生成
  • 包含
    • PptxGenerator
    • 渲染方法:_render_text(), _render_image(), _render_shape(), _render_table()
    • 元素分发:_render_element()
  • 特点
    • 渲染器内置在生成器中
    • 使用 isinstance() 检查元素类型
    • 通过元素对象的属性访问数据

8. renderers/html_renderer.py渲染层 - HTML

  • 职责HTML 预览渲染
  • 包含
    • HtmlRenderer
    • 渲染方法:render_text(), render_image(), render_shape(), render_table()
  • 特点
    • 与 PptxRenderer 共享元素抽象层
    • 使用固定 DPI (96) 进行单位转换

9. preview/server.py预览层

  • 职责:浏览器预览和热重载
  • 包含
    • Flask 应用:create_flask_app()
    • 文件监听:YAMLChangeHandler
    • 预览服务器:start_preview_server()
    • HTML 模板:HTML_TEMPLATE, ERROR_TEMPLATE

开发规范

1. Python 环境

必须使用 uv 运行脚本

# 正确
uv run yaml2pptx.py input.yaml output.pptx

# 错误 - 严禁直接使用主机环境的 python
python yaml2pptx.py input.yaml output.pptx

依赖管理

  • 所有依赖在 yaml2pptx.py/// script 头部声明
  • uv 会自动安装依赖,无需手动 pip install

2. 命令行接口

子命令架构

# check - 验证 YAML 文件
uv run yaml2pptx.py check <input> [--template-dir <dir>]

# convert - 转换为 PPTX
uv run yaml2pptx.py convert <input> [output] [--template-dir <dir>] [--skip-validation] [--force]

# preview - 启动预览服务器
uv run yaml2pptx.py preview <input> [--template-dir <dir>] [--port <port>] [--host <host>] [--no-browser]

参数说明

  • --template-dir:所有命令通用,指定模板目录
  • --skip-validationconvert 专用,跳过自动验证
  • --force/-fconvert 专用,强制覆盖已存在文件
  • --portpreview 专用,指定端口(默认随机 30000-40000
  • --hostpreview 专用,指定主机地址(默认 127.0.0.1
  • --no-browserpreview 专用,不自动打开浏览器

3. 文件组织

代码文件

  • 每个模块文件控制在 150-300 行
  • 入口脚本约 200 行
  • 使用有意义的文件名和目录结构

测试文件

  • 所有测试文件、临时文件必须放在 temp/ 目录下
  • 不污染项目根目录

4. 代码风格

导入顺序

# 1. 标准库
import sys
from pathlib import Path

# 2. 第三方库
import yaml
from pptx import Presentation

# 3. 本地模块
from utils import log_info
from core.elements import TextElement

文档字符串

  • 每个模块必须有模块级文档字符串
  • 每个类和函数必须有文档字符串
  • 使用中文编写注释和文档

命名规范

  • 模块名:小写 + 下划线(如 yaml_loader.py
  • 类名:大驼峰(如 TextElement
  • 函数名:小写 + 下划线(如 load_yaml_file()
  • 常量:大写 + 下划线(如 HTML_TEMPLATE

技术决策

1. 元素抽象层使用 dataclass

决策:使用 Python dataclass 定义元素数据类

理由

  • 简洁性:自动生成 __init____repr__ 等方法
  • 类型提示支持类型注解IDE 友好
  • 验证时机:__post_init__ 在创建时自动调用
  • 可扩展性:未来可以添加方法

示例

@dataclass
class TextElement:
    type: str = 'text'
    content: str = ''
    box: list = field(default_factory=lambda: [1, 1, 8, 1])
    font: dict = field(default_factory=dict)

    def __post_init__(self):
        if len(self.box) != 4:
            raise ValueError("box 必须包含 4 个数字")

2. 渲染器内置在生成器中

决策:将渲染逻辑内置在 PptxGenerator 和 HtmlRenderer 类中

理由

  • 封装性:渲染逻辑与生成器紧密相关
  • 简单性:不需要额外的渲染器接口
  • 性能:避免额外的方法调用开销

示例

class PptxGenerator:
    def _render_element(self, slide, elem, base_path):
        if isinstance(elem, TextElement):
            self._render_text(slide, elem)
        elif isinstance(elem, ImageElement):
            self._render_image(slide, elem, base_path)

3. 创建时验证

决策:在元素对象创建时进行验证(__post_init__ 方法)

理由

  • 尽早失败:在数据进入系统时就发现错误
  • 清晰的错误位置:堆栈指向元素创建处
  • 避免无效状态:确保元素对象始终有效

4. 元素工厂函数

决策:提供 create_element(elem_dict) 工厂函数

理由

  • 统一入口:所有元素创建都通过工厂函数
  • 类型安全:进行类型检查
  • 易于扩展:添加新元素类型只需添加一个分支

5. 验证职责分层

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

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

理由

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

元素级验证职责

  • 必需字段检查(在 __post_init__ 中)
  • 数据类型检查(在 __post_init__ 中)
  • 值的有效性检查(在 validate() 方法中)
    • 颜色格式验证
    • 字体大小合理性
    • 枚举值检查(如形状类型)
    • 表格数据一致性

系统级验证职责

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

示例

# 元素级验证(在元素类中)
@dataclass
class TextElement:
    def validate(self) -> List[ValidationIssue]:
        issues = []
        if self.font.get('color'):
            if not _is_valid_color(self.font['color']):
                issues.append(ValidationIssue(
                    level="ERROR",
                    message=f"无效的颜色格式: {self.font['color']}",
                    code="INVALID_COLOR_FORMAT"
                ))
        return issues

# 系统级验证(在验证器中)
class GeometryValidator:
    def validate_element(self, element, slide_index, elem_index):
        # 需要页面尺寸信息
        if element.box[0] + element.box[2] > self.slide_width:
            # 报告边界超出

6. 验证容忍度

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

理由

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

实现

TOLERANCE = 0.1  # 英寸

if right > slide_width + TOLERANCE:
    # 报告 WARNING

扩展指南

添加新元素类型

假设要添加 VideoElement

1. 在 core/elements.py 中定义数据类

@dataclass
class VideoElement:
    type: str = 'video'
    src: str = ''
    box: list = field(default_factory=lambda: [1, 1, 4, 3])
    autoplay: bool = False

    def __post_init__(self):
        if not self.src:
            raise ValueError("视频元素必须指定 src")
        if len(self.box) != 4:
            raise ValueError("box 必须包含 4 个数字")

2. 在工厂函数中添加分支

def create_element(elem_dict: dict):
    elem_type = elem_dict.get('type')
    # ... 其他类型 ...
    elif elem_type == 'video':
        return VideoElement(**elem_dict)

3. 在 PptxGenerator 中实现渲染方法

def _render_element(self, slide, elem, base_path):
    # ... 其他类型 ...
    elif isinstance(elem, VideoElement):
        self._render_video(slide, elem, base_path)

def _render_video(self, slide, elem: VideoElement, base_path):
    # 实现视频渲染逻辑
    pass

4. 在 HtmlRenderer 中实现渲染方法

def render_slide(self, slide_data, index, base_path):
    # ... 其他类型 ...
    elif isinstance(elem, VideoElement):
        elements_html += self.render_video(elem, base_path)

def render_video(self, elem: VideoElement, base_path):
    # 实现 HTML 视频渲染
    return f'<video src="{elem.src}" ...></video>'

添加新渲染器

假设要添加 PDF 渲染器:

1. 创建 renderers/pdf_renderer.py

from core.elements import TextElement, ImageElement, ShapeElement, TableElement

class PdfRenderer:
    def __init__(self):
        # 初始化 PDF 库
        pass

    def add_slide(self, slide_data, base_path=None):
        # 添加页面
        pass

    def _render_element(self, page, elem, base_path):
        if isinstance(elem, TextElement):
            self._render_text(page, elem)
        # ... 其他元素类型

2. 在 yaml2pptx.py 中添加 PDF 模式

from renderers.pdf_renderer import PdfRenderer

def main():
    # ... 解析参数 ...
    if args.pdf:
        # PDF 生成模式
        generator = PdfRenderer()
        # ... 渲染逻辑

测试规范

运行测试

# 验证 YAML 文件
uv run yaml2pptx.py check temp/test.yaml

# 使用模板时验证
uv run yaml2pptx.py check temp/demo.yaml --template-dir temp/templates

# 转换 YAML 为 PPTX
uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx

# 自动生成输出文件名
uv run yaml2pptx.py convert temp/test.yaml

# 跳过自动验证
uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx --skip-validation

# 强制覆盖已存在文件
uv run yaml2pptx.py convert temp/test.yaml temp/output.pptx --force

# 使用模板
uv run yaml2pptx.py convert temp/demo.yaml temp/output.pptx --template-dir temp/templates

# 启动预览服务器
uv run yaml2pptx.py preview temp/test.yaml

# 指定端口
uv run yaml2pptx.py preview temp/test.yaml --port 8080

# 允许局域网访问
uv run yaml2pptx.py preview temp/test.yaml --host 0.0.0.0

# 不自动打开浏览器
uv run yaml2pptx.py preview temp/test.yaml --no-browser

测试文件位置

所有测试文件必须放在 temp/ 目录下:

  • temp/*.yaml - 测试用的 YAML 文件
  • temp/*.pptx - 生成的 PPTX 文件
  • temp/templates/ - 测试用的模板文件

常见问题

Q: 为什么不能直接使用 python 运行脚本?

A: 项目使用 uv 的 Inline script metadata 来管理依赖。直接使用 python 会导致依赖缺失。必须使用 uv run yaml2pptx.py

Q: 如何添加新的依赖?

A: 在 yaml2pptx.py/// script 头部添加:

# /// script
# requires-python = ">=3.8"
# dependencies = [
#     "python-pptx",
#     "pyyaml",
#     "flask",
#     "watchdog",
#     "new-dependency",  # 添加新依赖
# ]
# ///

Q: 为什么元素使用 dataclass 而不是普通字典?

A: dataclass 提供:

  1. 类型安全和 IDE 支持
  2. 自动生成的方法(__init__, __repr__
  3. 创建时验证(__post_init__
  4. 更好的可维护性和可扩展性

Q: 如何调试渲染问题?

A: 使用预览模式:

uv run yaml2pptx.py preview temp/test.yaml

在浏览器中查看渲染结果,支持热重载。

项目约束

  1. 面向中文开发者:注释、文档、错误消息使用中文
  2. 使用 uv 运行:严禁直接使用主机环境的 python
  3. 测试文件隔离:所有测试文件放在 temp/ 目录
  4. 不污染主机环境:不修改主机的 Python 配置

维护指南

代码审查要点

  • 模块文件大小合理150-300 行)
  • 无循环依赖
  • 所有类和函数有文档字符串
  • 使用中文注释
  • 元素验证在 __post_init__ 中完成
  • 导入语句按标准库、第三方库、本地模块排序
  • 测试文件在 temp/ 目录下

性能优化建议

  1. 模板缓存Presentation 类已实现模板缓存
  2. 元素验证:只在创建时验证一次,渲染时不再验证
  3. 文件监听:预览模式使用 watchdog 高效监听文件变化