实现了统一的metadata结构和字体作用域系统,支持文档和模板库之间的单向字体引用。 主要变更: - 模板库必须包含metadata字段(包括size、fonts、fonts_default) - 实现文档和模板库的size一致性校验 - 实现字体作用域系统(文档可引用模板库字体,反之不可) - 实现跨域循环引用检测 - 实现fonts_default级联规则(模板库→文档→系统默认) - 添加错误代码常量(SIZE_MISMATCH、FONT_NOT_FOUND等) - 更新文档和开发者指南 测试覆盖: - 新增33个测试(单元测试20个,集成测试13个) - 所有457个测试通过 Breaking Changes: - 模板库文件必须包含metadata字段 - 模板库metadata.size为必填字段 - 文档和模板库的size必须一致 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
297 lines
12 KiB
Markdown
297 lines
12 KiB
Markdown
# Template Library
|
||
|
||
## Purpose
|
||
|
||
Template library 提供集中的模板管理机制,允许将多个模板定义存储在单个 YAML 文件中,通过模板名称引用。模板库支持独立的 metadata 结构,包括字体主题和尺寸定义。
|
||
|
||
## Requirements
|
||
|
||
### Requirement: 模板库文件必须包含 templates 字段
|
||
|
||
模板库文件 SHALL 包含必需的 `templates` 字段,该字段为字典类型,键为模板名称,值为模板定义。
|
||
|
||
#### Scenario: 验证模板库文件格式
|
||
|
||
- **WHEN** 系统加载模板库文件
|
||
- **THEN** 系统验证文件包含 `templates` 字段
|
||
- **AND** `templates` 字段必须为字典类型
|
||
|
||
#### Scenario: 模板库文件缺少 templates 字段时报错
|
||
|
||
- **WHEN** 模板库文件不包含 `templates` 字段
|
||
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_LIBRARY_MISSING_TEMPLATES_FIELD`
|
||
- **AND** 错误消息包含"缺少必需字段 'templates'"
|
||
|
||
#### Scenario: templates 字段类型错误时报错
|
||
|
||
- **WHEN** 模板库文件的 `templates` 字段不是字典类型
|
||
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_LIBRARY_MISSING_TEMPLATES_FIELD`
|
||
- **AND** 错误消息包含"'templates' 必须是字典"
|
||
|
||
### Requirement: 模板库文件必须包含元数据字段
|
||
|
||
模板库文件 SHALL 必须包含 metadata 字段,元数据结构与文档相同,包含 `size`(必填)、`version`、`author`、`description`、`fonts`、`fonts_default`。
|
||
|
||
#### Scenario: 加载包含完整元数据的模板库文件
|
||
|
||
- **WHEN** 模板库文件包含 metadata 字段,包含 size、version、author、description、fonts、fonts_default
|
||
- **THEN** 系统成功加载这些元数据字段
|
||
- **AND** size 字段必须为有效值
|
||
- **AND** fonts 和 fonts_default 按字体主题规则验证
|
||
|
||
#### Scenario: 模板库 metadata 缺少必填的 size 字段
|
||
|
||
- **WHEN** 模板库 metadata 中缺少 size 字段
|
||
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_LIBRARY_METADATA_MISSING_SIZE`
|
||
|
||
#### Scenario: 模板库缺少 metadata 字段
|
||
|
||
- **WHEN** 模板库文件不包含 metadata 字段
|
||
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_LIBRARY_MISSING_METADATA`
|
||
- **AND** 错误消息包含"模板库必须包含 metadata 字段"
|
||
|
||
### Requirement: 模板库必须包含 metadata 结构
|
||
|
||
模板库文件 SHALL 必须包含 metadata 字段,与文档使用相同的结构,包含 `size`(必填)、`version`、`author`、`description`、`fonts`、`fonts_default`。
|
||
|
||
#### Scenario: 模板库包含完整的 metadata
|
||
|
||
- **WHEN** 模板库文件包含 metadata 字段,包含 size、version、author、description、fonts、fonts_default
|
||
- **THEN** 系统成功解析并存储这些元数据
|
||
|
||
#### Scenario: 模板库缺少 metadata 字段
|
||
|
||
- **WHEN** 模板库文件不包含 metadata 字段
|
||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_LIBRARY_MISSING_METADATA`
|
||
- **AND** 错误消息包含"模板库必须包含 metadata 字段"
|
||
|
||
#### Scenario: 模板库 metadata 缺少 size 字段
|
||
|
||
- **WHEN** 模板库包含 metadata 但缺少 size 字段
|
||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_LIBRARY_METADATA_MISSING_SIZE`
|
||
- **AND** 错误消息包含"模板库 metadata 缺少必填字段 'size'"
|
||
|
||
#### Scenario: 模板库 metadata 的 size 值无效
|
||
|
||
- **WHEN** 模板库 metadata.size 不是有效的尺寸值(如 "16:9" 或 "4:3")
|
||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_LIBRARY_METADATA_INVALID_SIZE`
|
||
|
||
### Requirement: 模板库 metadata 必须使用与文档相同的验证规则
|
||
|
||
模板库 metadata 的字段验证 SHALL 与文档 metadata 使用相同的规则。
|
||
|
||
#### Scenario: 模板库 metadata.fonts 验证
|
||
|
||
- **WHEN** 模板库 metadata.fonts 中定义字体配置
|
||
- **THEN** 系统使用与文档 fonts 相同的验证规则
|
||
|
||
#### Scenario: 模板库 metadata.fonts_default 验证
|
||
|
||
- **WHEN** 模板库 metadata.fonts_default 引用字体配置
|
||
- **THEN** 系统验证引用存在于模板库 metadata.fonts 中
|
||
|
||
#### Scenario: 模板库 metadata.version 和 author 可选
|
||
|
||
- **WHEN** 模板库 metadata 包含或不含 version、author 字段
|
||
- **THEN** 系统正常处理,这些字段为可选项
|
||
|
||
### Requirement: 系统必须校验文档和模板库的 size 一致性
|
||
|
||
系统 SHALL 在加载模板库时校验文档的 metadata.size 与模板库的 metadata.size 是否一致。
|
||
|
||
#### Scenario: 文档和模板库 size 一致
|
||
|
||
- **WHEN** 文档 metadata.size 为 "16:9" 且模板库 metadata.size 也为 "16:9"
|
||
- **THEN** 系统正常加载,不报错
|
||
|
||
#### Scenario: 文档和模板库 size 不一致
|
||
|
||
- **WHEN** 文档 metadata.size 为 "16:9" 但模板库 metadata.size 为 "4:3"
|
||
- **THEN** 系统抛出 ERROR,错误代码为 `SIZE_MISMATCH`
|
||
- **AND** 错误消息包含"文档尺寸 '16:9' 与模板库尺寸 '4:3' 不一致"
|
||
|
||
### Requirement: 模板库 fonts 的 parent 只能引用模板库内部
|
||
|
||
模板库 metadata.fonts 中定义的字体配置,其 parent 字段 SHALL 只能引用模板库内部的字体配置。
|
||
|
||
#### Scenario: parent 引用模板库内部的字体
|
||
|
||
- **WHEN** 模板库 metadata.fonts 中定义 heading: {parent: "@base"} 且 base 存在于模板库 fonts 中
|
||
- **THEN** 系统成功解析继承关系
|
||
|
||
#### Scenario: parent 引用文档的字体
|
||
|
||
- **WHEN** 模板库 metadata.fonts 中定义 custom: {parent: "@doc-font"}
|
||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_PARENT_REF_DOC_FORBIDDEN`
|
||
- **AND** 错误消息包含"模板库字段的 parent 不能引用文档的字体配置"
|
||
|
||
#### Scenario: parent 引用不存在的字体
|
||
|
||
- **WHEN** 模板库 metadata.fonts 中定义 heading: {parent: "@nonexistent"}
|
||
- **THEN** 系统抛出 ERROR,错误代码为 `FONT_NOT_FOUND`
|
||
- **AND** 错误消息包含"引用的字体配置不存在"
|
||
|
||
### Requirement: 外部模板元素只能引用模板库的 fonts
|
||
|
||
外部模板(模板库中定义的模板)中的元素 SHALL 只能引用模板库 metadata.fonts 中定义的字体配置。
|
||
|
||
#### Scenario: 模板元素引用模板库的字体
|
||
|
||
- **WHEN** 外部模板元素定义 font: "@title" 且 title 存在于模板库 fonts 中
|
||
- **THEN** 系统成功应用字体配置
|
||
|
||
#### Scenario: 模板元素引用文档的字体
|
||
|
||
- **WHEN** 外部模板元素定义 font: "@doc-title" 且 doc-title 只存在于文档 fonts 中
|
||
- **THEN** 系统抛出 ERROR,错误代码为 `TEMPLATE_FONT_REF_DOC_FORBIDDEN`
|
||
- **AND** 错误消息包含"模板元素不能引用文档的字体配置"
|
||
|
||
#### Scenario: 模板元素引用同名字体(只使用模板库的)
|
||
|
||
- **WHEN** 外部模板元素定义 font: "@title"
|
||
- **AND** title 同时存在于文档 fonts 和模板库 fonts 中
|
||
- **THEN** 系统只使用模板库的 title 配置(不查找文档)
|
||
|
||
### Requirement: 模板元素的 fonts_default 级联规则
|
||
|
||
外部模板元素未定义 font 时 SHALL 级联使用 fonts_default:优先文档 fonts_default,文档没有则使用模板库 fonts_default。
|
||
|
||
#### Scenario: 模板元素未定义 font,文档有 fonts_default
|
||
|
||
- **WHEN** 模板元素未定义 font
|
||
- **AND** 文档定义了 metadata.fonts_default: "@body"
|
||
- **THEN** 元素使用文档的 fonts_default 配置
|
||
|
||
#### Scenario: 模板元素未定义 font,文档没有 fonts_default
|
||
|
||
- **WHEN** 模板元素未定义 font
|
||
- **AND** 文档未定义 fonts_default
|
||
- **AND** 模板库定义了 metadata.fonts_default: "@base"
|
||
- **THEN** 元素使用模板库的 fonts_default 配置
|
||
|
||
#### Scenario: 模板元素未定义 font,都没有 fonts_default
|
||
|
||
- **WHEN** 模板元素未定义 font
|
||
- **AND** 文档和模板库都未定义 fonts_default
|
||
- **THEN** 元素使用系统默认字体
|
||
|
||
### Requirement: 表格元素的双字体字段应用相同规则
|
||
|
||
表格元素的 font 和 header_font 字段 SHALL 应用与普通元素相同的字体引用规则。
|
||
|
||
#### Scenario: 表格的 font 引用模板库字体
|
||
|
||
- **WHEN** 模板中的表格定义 font: "@table-body"
|
||
- **AND** table-body 存在于模板库 fonts 中
|
||
- **THEN** 系统成功应用字体配置
|
||
|
||
#### Scenario: 表格的 header_font 引用模板库字体
|
||
|
||
- **WHEN** 模板中的表格定义 header_font: "@table-header"
|
||
- **AND** table-header 存在于模板库 fonts 中
|
||
- **THEN** 系统成功应用字体配置
|
||
|
||
#### Scenario: 表格的 font 为 null
|
||
|
||
- **WHEN** 模板中的表格定义 font: null 或未定义
|
||
- **THEN** 系统按 fonts_default 级联规则处理
|
||
|
||
### Requirement: 模板库文件必须递归验证每个模板的结构
|
||
|
||
系统 SHALL 对模板库中的每个模板递归验证其结构,包括 `vars` 和 `elements` 字段的正确性。
|
||
|
||
#### Scenario: 验证模板的 vars 字段
|
||
|
||
- **WHEN** 模板库中的模板定义了 `vars` 字段
|
||
- **THEN** 系统验证 `vars` 为列表类型
|
||
- **AND** 验证每个变量定义包含 `name` 字段
|
||
- **AND** 如果 `vars` 存在但不为列表,抛出错误
|
||
|
||
#### Scenario: 验证模板的 elements 字段
|
||
|
||
- **WHEN** 模板库中的模板定义了 `elements` 字段
|
||
- **THEN** 系统验证 `elements` 为列表类型
|
||
- **AND** 验证 `elements` 字段存在且不为空
|
||
- **AND** 如果 `elements` 不存在或为空,抛出错误
|
||
|
||
#### Scenario: 验证多个模板的结构
|
||
|
||
- **WHEN** 模板库包含多个模板定义
|
||
- **THEN** 系统验证每个模板的结构完整性
|
||
- **AND** 如果某个模板结构错误,错误消息包含模板名称和具体位置
|
||
|
||
### Requirement: 系统必须从模板库文件按名称查询模板
|
||
|
||
系统 SHALL 支持通过模板名称从模板库文件中查询模板,查询方式为字典键查找。
|
||
|
||
#### Scenario: 通过名称从模板库加载模板
|
||
|
||
- **WHEN** 幻灯片指定 `template: title-slide`
|
||
- **AND** 用户提供 `--template /path/to/templates.yaml`
|
||
- **THEN** 系统从模板库文件的 `templates.title-slide` 加载模板定义
|
||
|
||
#### Scenario: 模板名称不存在时报错
|
||
|
||
- **WHEN** 幻灯片引用的模板名称在模板库中不存在
|
||
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_NOT_FOUND_IN_LIBRARY`
|
||
- **AND** 错误消息包含"模板库中找不到指定模板名称: <模板名>"
|
||
|
||
#### Scenario: 模板库文件不存在时报错
|
||
|
||
- **WHEN** 用户提供的 `--template` 文件路径不存在
|
||
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_LIBRARY_FILE_NOT_FOUND`
|
||
- **AND** 错误消息包含"模板库文件不存在"
|
||
|
||
### Requirement: 模板库中的图片资源路径必须相对于模板库文件所在目录
|
||
|
||
系统 SHALL 在渲染外部模板时,将图片元素的相对路径解析为相对于模板库文件所在目录的绝对路径。
|
||
|
||
#### Scenario: 解析模板中的图片相对路径
|
||
|
||
- **WHEN** 模板库文件位于 `/lib/theme.yaml`
|
||
- **AND** 模板中的图片元素定义 `src: "./assets/logo.png"`
|
||
- **THEN** 系统将路径解析为 `/lib/assets/logo.png`
|
||
|
||
#### Scenario: 图片绝对路径保持不变
|
||
|
||
- **WHEN** 模板中的图片元素定义绝对路径 `src: "/usr/share/images/logo.png"`
|
||
- **THEN** 系统不修改该路径
|
||
|
||
#### Scenario: 图片路径解析失败时显示完整路径
|
||
|
||
- **WHEN** 解析后的图片路径不存在
|
||
- **THEN** 错误消息显示解析后的绝对路径
|
||
- **AND** 用户可以准确找到问题文件位置
|
||
|
||
### Requirement: 模板库加载不应缓存模板
|
||
|
||
系统 SHALL 每次从模板库加载模板时创建新的模板对象,不使用缓存机制。
|
||
|
||
#### Scenario: 每次加载创建新对象
|
||
|
||
- **WHEN** 多个幻灯片使用同一个模板名称
|
||
- **THEN** 系统每次都从模板库重新加载模板定义
|
||
- **AND** 每次创建新的 Template 对象
|
||
|
||
#### Scenario: 模板库文件修改后立即生效
|
||
|
||
- **WHEN** 模板库文件在运行时被修改
|
||
- **THEN** 下一次加载模板时使用新的定义
|
||
- **AND** 不需要重启程序
|
||
|
||
### Requirement: 命令行参数必须支持 --template 指定模板库文件
|
||
|
||
系统 SHALL 支持 `--template` 参数指定模板库文件路径,替代原有的 `--template-dir` 参数。
|
||
|
||
#### Scenario: 使用 --template 参数
|
||
|
||
- **WHEN** 用户执行 `yaml2pptx.py convert input.yaml --template /path/to/theme.yaml`
|
||
- **THEN** 系统加载 `/path/to/theme.yaml` 作为模板库文件
|
||
- **AND** 可以从该文件引用模板
|
||
|
||
#### Scenario: --template 参数可选
|
||
|
||
- **WHEN** 演示文稿仅使用内联模板或自定义元素
|
||
- **THEN** 用户可以不提供 `--template` 参数
|
||
- **AND** 系统正常处理
|