Refactor yaml2pptx.py from a 1,245-line monolithic script into a modular architecture with clear separation of concerns. The entry point is now 127 lines, with business logic distributed across focused modules. Architecture: - core/: Domain models (elements, template, presentation) - loaders/: YAML loading and validation - renderers/: PPTX and HTML rendering - preview/: Flask preview server - utils.py: Shared utilities Key improvements: - Element abstraction layer using dataclass with validation - Renderer logic built into generator classes - Single-direction dependencies (no circular imports) - Each module 150-300 lines for better readability - Backward compatible CLI interface Documentation: - README.md: User-facing usage guide - README_DEV.md: Developer documentation OpenSpec: - Archive refactor-yaml2pptx-modular change (63/70 tasks complete) - Sync 5 delta specs to main specs (2 new + 3 updated)
303 lines
9.9 KiB
Markdown
303 lines
9.9 KiB
Markdown
# PPTX Generation
|
||
|
||
## Purpose
|
||
|
||
PPTX generation 系统负责使用 python-pptx 库创建符合 OOXML 标准的 PowerPoint 文档。它管理演示文稿的整体属性(如尺寸),按顺序添加幻灯片,并提供命令行接口供用户使用。
|
||
|
||
## Requirements
|
||
|
||
### Requirement: 系统必须创建符合 OOXML 标准的 PPTX 文件
|
||
|
||
系统 SHALL 使用 python-pptx 库生成符合 Office Open XML (OOXML) 标准的 .pptx 文件。
|
||
|
||
#### Scenario: 生成的 PPTX 文件可被 PowerPoint 打开
|
||
|
||
- **WHEN** 系统生成 PPTX 文件
|
||
- **THEN** 该文件可被 Microsoft PowerPoint 正常打开和编辑
|
||
|
||
#### Scenario: 生成的 PPTX 文件可被其他软件打开
|
||
|
||
- **WHEN** 系统生成 PPTX 文件
|
||
- **THEN** 该文件可被 LibreOffice Impress、Google Slides 等软件正常打开
|
||
|
||
#### Scenario: PPTX 文件扩展名正确
|
||
|
||
- **WHEN** 系统保存演示文稿
|
||
- **THEN** 输出文件的扩展名为 `.pptx`
|
||
|
||
### Requirement: 系统必须支持设置演示文稿尺寸
|
||
|
||
系统 SHALL 支持设置演示文稿的幻灯片尺寸,支持 16:9 和 4:3 两种标准比例。
|
||
|
||
#### Scenario: 创建 16:9 比例的演示文稿
|
||
|
||
- **WHEN** metadata 中指定 `size: "16:9"`
|
||
- **THEN** 系统创建 10×5.625 英寸的幻灯片
|
||
|
||
#### Scenario: 创建 4:3 比例的演示文稿
|
||
|
||
- **WHEN** metadata 中指定 `size: "4:3"`
|
||
- **THEN** 系统创建 10×7.5 英寸的幻灯片
|
||
|
||
#### Scenario: 默认使用 16:9 比例
|
||
|
||
- **WHEN** metadata 中未指定 size 字段
|
||
- **THEN** 系统默认使用 16:9 比例
|
||
|
||
#### Scenario: 不支持的尺寸比例报错
|
||
|
||
- **WHEN** metadata 中指定了不支持的尺寸比例(如 "21:9")
|
||
- **THEN** 系统抛出错误,提示仅支持 16:9 和 4:3
|
||
|
||
### Requirement: 系统必须按顺序添加幻灯片
|
||
|
||
系统 SHALL 按照 YAML 中 slides 列表的顺序,依次添加幻灯片到 PPTX 文件。
|
||
|
||
#### Scenario: 幻灯片顺序与 YAML 一致
|
||
|
||
- **WHEN** YAML 中 slides 列表为 `[slide1, slide2, slide3]`
|
||
- **THEN** 生成的 PPTX 文件中,幻灯片顺序为 slide1、slide2、slide3
|
||
|
||
#### Scenario: 空幻灯片列表生成空演示文稿
|
||
|
||
- **WHEN** YAML 中 slides 列表为空
|
||
- **THEN** 系统生成一个不包含任何幻灯片的 PPTX 文件(或抛出警告)
|
||
|
||
### Requirement: 系统必须使用空白布局
|
||
|
||
系统 SHALL 为每个幻灯片使用空白布局(blank layout),以便完全自定义内容。
|
||
|
||
#### Scenario: 使用空白布局添加幻灯片
|
||
|
||
- **WHEN** 系统添加幻灯片
|
||
- **THEN** 使用 `prs.slide_layouts[6]`(空白布局)而非预定义布局
|
||
|
||
#### Scenario: 空白幻灯片不包含占位符
|
||
|
||
- **WHEN** 创建新的空白幻灯片
|
||
- **THEN** 幻灯片不包含任何预定义的标题或内容占位符
|
||
|
||
### Requirement: 系统必须保存到指定路径
|
||
|
||
系统 SHALL 将生成的 PPTX 文件保存到用户指定的路径。
|
||
|
||
#### Scenario: 保存到指定文件路径
|
||
|
||
- **WHEN** 用户指定输出路径为 `output/presentation.pptx`
|
||
- **THEN** 系统将 PPTX 文件保存到该路径
|
||
|
||
#### Scenario: 自动创建输出目录
|
||
|
||
- **WHEN** 输出路径包含不存在的目录(如 `output/subdir/file.pptx`)
|
||
- **THEN** 系统自动创建所需的目录结构
|
||
|
||
#### Scenario: 输出路径已存在时覆盖
|
||
|
||
- **WHEN** 指定的输出文件路径已存在
|
||
- **THEN** 系统覆盖原有文件(或提供选项询问用户)
|
||
|
||
#### Scenario: 无写入权限时报错
|
||
|
||
- **WHEN** 输出路径没有写入权限
|
||
- **THEN** 系统抛出错误,提示权限不足
|
||
|
||
### Requirement: 系统必须使用 python-pptx 的单位转换函数
|
||
|
||
系统 SHALL 使用 python-pptx 提供的 Inches() 函数将英寸值转换为 EMU(English Metric Units)。
|
||
|
||
#### Scenario: 使用 Inches() 转换坐标
|
||
|
||
- **WHEN** 元素 box 定义为 `[1, 2, 8, 3]`
|
||
- **THEN** 系统调用 `Inches(1)`, `Inches(2)`, `Inches(8)`, `Inches(3)` 转换为 EMU
|
||
|
||
#### Scenario: EMU 转换的精度
|
||
|
||
- **WHEN** 使用 Inches(1.0)
|
||
- **THEN** 返回值为 914400 EMU(1 英寸 = 914400 EMU)
|
||
|
||
### Requirement: 系统必须处理颜色转换
|
||
|
||
系统 SHALL 将十六进制颜色值(如 "#4a90e2")转换为 python-pptx 的 RGBColor 对象。
|
||
|
||
#### Scenario: 十六进制颜色转 RGB
|
||
|
||
- **WHEN** 颜色值为 "#4a90e2"
|
||
- **THEN** 系统转换为 RGBColor(74, 144, 226)
|
||
|
||
#### Scenario: 短格式十六进制颜色
|
||
|
||
- **WHEN** 颜色值为 "#fff"(短格式)
|
||
- **THEN** 系统扩展为 "#ffffff" 并转换为 RGBColor(255, 255, 255)
|
||
|
||
#### Scenario: 无效颜色格式报错
|
||
|
||
- **WHEN** 颜色值不是有效的十六进制格式
|
||
- **THEN** 系统抛出错误,提示颜色格式无效
|
||
|
||
### Requirement: 系统必须使用 uv 运行 Python 脚本
|
||
|
||
系统 SHALL 使用 uv 运行转换脚本,通过 Inline script metadata 指定依赖。
|
||
|
||
#### Scenario: 脚本包含 Inline script metadata
|
||
|
||
- **WHEN** 查看 `yaml2pptx.py` 文件头部
|
||
- **THEN** 包含 `# /// script` 块,定义 python-pptx 和 PyYAML 依赖
|
||
|
||
#### Scenario: 使用 uv 运行脚本
|
||
|
||
- **WHEN** 执行转换命令
|
||
- **THEN** 使用 `uv run yaml2pptx.py` 而非 `python yaml2pptx.py`
|
||
|
||
#### Scenario: 禁止直接安装依赖
|
||
|
||
- **WHEN** 需要使用 python-pptx 或 PyYAML
|
||
- **THEN** 不使用 `pip install`,而是在 script metadata 中声明依赖
|
||
|
||
### Requirement: PptxGenerator 必须内置渲染器
|
||
|
||
系统 SHALL 将 PptxGenerator 类重构为内置渲染器的架构,渲染逻辑作为类的私有方法,而不是独立的函数。
|
||
|
||
#### Scenario: PptxGenerator 包含渲染方法
|
||
|
||
- **WHEN** 开发者查看 PptxGenerator 类
|
||
- **THEN** 应包含 `_render_element()`, `_render_text()`, `_render_image()`, `_render_shape()`, `_render_table()` 等私有方法
|
||
|
||
#### Scenario: add_slide 方法调用内置渲染器
|
||
|
||
- **WHEN** 调用 `generator.add_slide(slide_data, base_path)`
|
||
- **THEN** 系统应在方法内部调用 `_render_element()` 来渲染每个元素
|
||
|
||
#### Scenario: 渲染方法接收元素对象
|
||
|
||
- **WHEN** 渲染方法被调用
|
||
- **THEN** 应接收元素对象(如 TextElement, ImageElement)而不是字典
|
||
|
||
### Requirement: PptxGenerator 必须位于 renderers 模块
|
||
|
||
系统 SHALL 将 PptxGenerator 类从主脚本提取到 `renderers/pptx_renderer.py` 模块中。
|
||
|
||
#### Scenario: PptxGenerator 在独立模块中
|
||
|
||
- **WHEN** 开发者查看项目结构
|
||
- **THEN** PptxGenerator 类应定义在 `renderers/pptx_renderer.py` 文件中
|
||
|
||
#### Scenario: 主脚本导入 PptxGenerator
|
||
|
||
- **WHEN** yaml2pptx.py 需要使用 PptxGenerator
|
||
- **THEN** 应通过 `from renderers.pptx_renderer import PptxGenerator` 导入
|
||
|
||
### Requirement: 渲染器必须通过类型检查分发元素
|
||
|
||
系统 SHALL 在 `_render_element()` 方法中使用 `isinstance()` 检查元素类型,分发到对应的渲染方法。
|
||
|
||
#### Scenario: 分发文本元素
|
||
|
||
- **WHEN** `_render_element()` 接收到 TextElement 对象
|
||
- **THEN** 系统应调用 `_render_text(slide, elem)`
|
||
|
||
#### Scenario: 分发图片元素
|
||
|
||
- **WHEN** `_render_element()` 接收到 ImageElement 对象
|
||
- **THEN** 系统应调用 `_render_image(slide, elem, base_path)`
|
||
|
||
#### Scenario: 分发形状元素
|
||
|
||
- **WHEN** `_render_element()` 接收到 ShapeElement 对象
|
||
- **THEN** 系统应调用 `_render_shape(slide, elem)`
|
||
|
||
#### Scenario: 分发表格元素
|
||
|
||
- **WHEN** `_render_element()` 接收到 TableElement 对象
|
||
- **THEN** 系统应调用 `_render_table(slide, elem)`
|
||
|
||
### Requirement: 渲染方法必须访问元素对象的属性
|
||
|
||
系统 SHALL 通过元素对象的属性(如 `elem.content`, `elem.box`)访问数据,而不是通过字典的 `get()` 方法。
|
||
|
||
#### Scenario: 访问文本元素属性
|
||
|
||
- **WHEN** `_render_text()` 渲染文本元素
|
||
- **THEN** 应使用 `elem.content`, `elem.box`, `elem.font` 等属性
|
||
|
||
#### Scenario: 访问图片元素属性
|
||
|
||
- **WHEN** `_render_image()` 渲染图片元素
|
||
- **THEN** 应使用 `elem.src`, `elem.box` 等属性
|
||
|
||
#### Scenario: 访问形状元素属性
|
||
|
||
- **WHEN** `_render_shape()` 渲染形状元素
|
||
- **THEN** 应使用 `elem.shape`, `elem.box`, `elem.fill`, `elem.line` 等属性
|
||
|
||
#### Scenario: 访问表格元素属性
|
||
|
||
- **WHEN** `_render_table()` 渲染表格元素
|
||
- **THEN** 应使用 `elem.data`, `elem.position`, `elem.col_widths`, `elem.style` 等属性
|
||
|
||
### Requirement: 系统架构保持简洁
|
||
|
||
系统 SHALL 采用两层架构(模板 + 演示文稿),颜色和样式直接在模板中定义。
|
||
|
||
#### Scenario: 模板自包含样式
|
||
|
||
- **WHEN** 查看模板文件
|
||
- **THEN** 颜色值直接以十六进制格式指定(如 "#4a90e2")
|
||
|
||
#### Scenario: 无需主题配置
|
||
|
||
- **WHEN** 创建新的演示文稿
|
||
- **THEN** metadata 中不需要指定 theme 字段
|
||
|
||
#### Scenario: 模板独立配色
|
||
|
||
- **WHEN** 创建新模板
|
||
- **THEN** 可以为该模板定义独立的颜色方案,不受其他模板影响
|
||
|
||
### Requirement: 系统必须提供命令行接口
|
||
|
||
系统 SHALL 提供命令行接口,接受输入 YAML 文件路径和输出 PPTX 文件路径。
|
||
|
||
#### Scenario: 基本命令行使用
|
||
|
||
- **WHEN** 执行 `uv run yaml2pptx.py input.yaml output.pptx`
|
||
- **THEN** 系统读取 input.yaml,生成 output.pptx
|
||
|
||
#### Scenario: 仅提供输入文件时自动命名输出
|
||
|
||
- **WHEN** 执行 `uv run yaml2pptx.py input.yaml`
|
||
- **THEN** 系统自动生成输出文件名为 `input.pptx`
|
||
|
||
#### Scenario: 显示帮助信息
|
||
|
||
- **WHEN** 执行 `uv run yaml2pptx.py --help`
|
||
- **THEN** 系统显示使用说明和参数列表
|
||
|
||
#### Scenario: 输入文件不存在时报错
|
||
|
||
- **WHEN** 提供的输入文件路径不存在
|
||
- **THEN** 系统抛出错误,提示输入文件未找到
|
||
|
||
### Requirement: 系统必须报告转换进度
|
||
|
||
系统 SHALL 在转换过程中输出进度信息,包括当前处理的幻灯片数量。
|
||
|
||
#### Scenario: 输出转换开始信息
|
||
|
||
- **WHEN** 开始转换
|
||
- **THEN** 系统输出 "开始转换: input.yaml"
|
||
|
||
#### Scenario: 输出幻灯片处理进度
|
||
|
||
- **WHEN** 处理每个幻灯片
|
||
- **THEN** 系统输出 "处理幻灯片 N/M"
|
||
|
||
#### Scenario: 输出转换完成信息
|
||
|
||
- **WHEN** 转换成功完成
|
||
- **THEN** 系统输出 "转换完成: output.pptx"
|
||
|
||
#### Scenario: 转换失败时输出错误
|
||
|
||
- **WHEN** 转换过程中发生错误
|
||
- **THEN** 系统输出详细的错误信息和堆栈跟踪(调试模式下)
|