1
0
Files
PPTX/README_DEV.md
lanyuanxiaoyao ed940f0690 refactor: modularize yaml2pptx into layered architecture
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)
2026-03-02 16:43:45 +08:00

12 KiB
Raw Blame History

开发文档

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

项目概述

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

代码结构

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

html2pptx/
├── yaml2pptx.py (127 行)          # 入口脚本CLI + main 函数
├── utils.py (74 行)               # 工具函数(日志、颜色转换)
├── core/                          # 核心领域模型
│   ├── elements.py (96 行)        # 元素抽象层dataclass
│   ├── template.py (191 行)       # 模板系统
│   └── presentation.py (91 行)    # 演示文稿类
├── loaders/                       # 数据加载层
│   └── yaml_loader.py (113 行)    # YAML 加载和验证
├── renderers/                     # 渲染层
│   ├── pptx_renderer.py (292 行)  # PPTX 渲染器
│   └── html_renderer.py (172 行)  # HTML 渲染器(预览)
└── preview/                       # 预览功能
    └── server.py (244 行)         # Flask 服务器 + 文件监听

依赖关系

yaml2pptx.py (入口)
    ↓
    ├─→ utils (工具函数)
    ├─→ loaders.yaml_loader (YAML 加载)
    ├─→ 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核心层 - 元素抽象)

  • 职责:定义元素数据类和工厂函数
  • 包含
    • TextElement - 文本元素dataclass
    • ImageElement - 图片元素dataclass
    • ShapeElement - 形状元素dataclass
    • TableElement - 表格元素dataclass
    • create_element() - 元素工厂函数
  • 特点
    • 使用 @dataclass 装饰器
    • __post_init__ 中进行验证
    • 创建时验证,尽早发现错误

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. 文件组织

代码文件

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

测试文件

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

3. 代码风格

导入顺序

# 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) 工厂函数

理由

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

扩展指南

添加新元素类型

假设要添加 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()
        # ... 渲染逻辑

测试规范

运行测试

# 基本 PPTX 生成
uv run yaml2pptx.py temp/test.yaml temp/output.pptx

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

# 预览模式
uv run yaml2pptx.py temp/test.yaml --preview

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

测试文件位置

所有测试文件必须放在 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 temp/test.yaml --preview

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

项目约束

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

维护指南

代码审查要点

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

性能优化建议

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