1
0
Files
lanyuanxiaoyao f1aae96a04 refactor: 重构外部模板系统,改为单文件模板库模式
主要变更:
- 将 templates_dir 参数改为 template_file,支持单个模板库 YAML 文件
- 添加模板库 YAML 验证功能
- 为模板添加 base_dir 支持,正确解析相对路径资源
- 内联模板与外部模板同名时改为警告(内联优先)
- 移除模板缓存机制,直接使用模板库字典
- 更新所有相关测试以适配新的模板加载方式

此重构简化了模板管理,使模板资源的路径解析更加清晰明确。
2026-03-05 13:27:12 +08:00

333 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Template System
## Purpose
Template system 提供可复用的幻灯片布局和样式定义。模板包含变量定义、元素列表,支持变量替换、条件渲染,以及从目录加载。颜色和样式直接在模板中定义,无需额外的主题抽象层。
## Requirements
### Requirement: 模板必须定义变量列表
模板 SHALL 包含 vars 字段,定义该模板需要的变量,包括变量名、是否必需、默认值等。
#### Scenario: 加载模板变量定义
- **WHEN** 模板文件定义了 `vars` 列表,包含 `name``required``default` 等字段
- **THEN** 系统成功加载变量定义,可通过模板对象访问
#### Scenario: 验证必需变量
- **WHEN** 模板定义了 `{name: title, required: true}` 的变量
- **THEN** 渲染时如果未提供该变量,系统抛出错误
#### Scenario: 使用变量默认值
- **WHEN** 模板定义了 `{name: subtitle, required: false, default: ""}` 的变量
- **THEN** 渲染时如果未提供该变量,系统使用空字符串作为默认值
### Requirement: 模板必须定义元素列表
模板 SHALL 包含 elements 字段,定义该模板的幻灯片布局和元素。
#### Scenario: 加载模板元素定义
- **WHEN** 模板文件定义了 `elements` 列表,包含文本、图片、形状等元素
- **THEN** 系统成功加载元素列表,准备渲染
#### Scenario: 元素包含模板变量引用
- **WHEN** 模板元素中包含 `{title}` 等模板变量引用
- **THEN** 系统在渲染时用用户提供的值替换变量
#### Scenario: 元素直接指定样式值
- **WHEN** 模板元素中直接指定 `color: "#4a90e2"` 等样式值
- **THEN** 系统正确应用该样式值
### Requirement: 系统必须支持模板渲染
系统 SHALL 能够根据用户提供的变量值渲染模板,生成实际的元素列表。
#### Scenario: 渲染包含变量的模板
- **WHEN** 用户提供 `{title: "Hello", subtitle: "World"}` 渲染模板
- **THEN** 系统将模板中的 `{title}` 替换为 "Hello"`{subtitle}` 替换为 "World"
#### Scenario: 数值类型自动转换
- **WHEN** 模板定义 `size: "{font_size}"` 且用户提供 `{font_size: "44"}`
- **THEN** 系统自动将字符串 "44" 转换为整数 44
#### Scenario: 检测未定义的模板变量
- **WHEN** 模板中引用了 `{undefined_var}`,但该变量未在 vars 中定义,也未由用户提供
- **THEN** 系统抛出错误,指出未定义的变量
### Requirement: 系统必须支持条件渲染
系统 SHALL 支持基于变量值的条件渲染,通过 `visible` 字段控制元素是否显示。条件表达式使用 simpleeval 引擎评估,支持复杂的逻辑判断、比较运算、成员测试和数学计算。
#### Scenario: 显示满足条件的元素
- **WHEN** 元素定义了 `visible: "{count > 0}"`,且用户提供的 count 大于 0
- **THEN** 系统渲染该元素
#### Scenario: 隐藏不满足条件的元素
- **WHEN** 元素定义了 `visible: "{count > 0}"`,但用户提供的 count 等于 0
- **THEN** 系统跳过该元素,不渲染到幻灯片中
#### Scenario: 复杂逻辑条件
- **WHEN** 元素定义了 `visible: "{count > 0 and status == 'active'}"`,且两个条件都满足
- **THEN** 系统渲染该元素
#### Scenario: 成员测试条件
- **WHEN** 元素定义了 `visible: "{status in ['draft', 'review']}"`,且 status 为 "draft"
- **THEN** 系统渲染该元素
#### Scenario: 数学运算条件
- **WHEN** 元素定义了 `visible: "{(price * discount) > 50}"`,且计算结果大于 50
- **THEN** 系统渲染该元素
#### Scenario: 条件表达式语法错误
- **WHEN** visible 字段包含无效的条件表达式(如 `{count > }`
- **THEN** 系统抛出错误,提示"条件表达式语法错误",并显示具体的语法问题
#### Scenario: 条件表达式中的变量未定义
- **WHEN** visible 字段引用了未定义的变量(如 `{undefined_var > 0}`
- **THEN** 系统抛出错误,提示"条件表达式中的变量未定义: undefined_var",并列出可用变量
#### Scenario: 条件表达式使用不支持的函数
- **WHEN** visible 字段使用了不在白名单中的函数(如 `{eval(code)}`
- **THEN** 系统抛出错误,提示"条件表达式中使用了不支持的函数: eval"
#### Scenario: 向后兼容的简单表达式
- **WHEN** 元素定义了 `visible: "{subtitle != ''}"`(旧语法格式)
- **THEN** 系统使用新的 simpleeval 引擎正确评估该表达式
### Requirement: 模板文件必须可从指定位置加载
系统 SHALL 支持从两个位置加载模板:内联模板(在文档 YAML 的 `templates` 字段中定义)和外部模板(通过 `--template` 参数指定的模板库文件)。
#### Scenario: 从内联模板加载
- **WHEN** 幻灯片指定 `template: title-slide`
- **AND** 文档 YAML 的 `templates` 字段定义了 `title-slide` 模板
- **THEN** 系统从内联模板字典中加载模板定义
#### Scenario: 从模板库文件加载
- **WHEN** 幻灯片指定 `template: content-slide`
- **AND** 用户提供 `--template /path/to/theme.yaml`
- **AND** 模板库文件的 `templates.content-slide` 存在
- **THEN** 系统从模板库文件中加载模板定义
#### Scenario: 模板库文件不存在时报错
- **WHEN** 幻灯片引用外部模板,但 `--template` 指定的文件不存在
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_LIBRARY_FILE_NOT_FOUND`
#### Scenario: 模板名称在两者中都不存在时报错
- **WHEN** 幻灯片引用的模板名称既不在内联模板中,也不在模板库中
- **THEN** 系统抛出错误,错误代码为 `TEMPLATE_NOT_FOUND_IN_LIBRARY`
- **AND** 错误消息包含"模板库中找不到指定模板名称"
### Requirement: 内联和外部模板同名时必须发出警告
系统 SHALL 检测内联模板和外部模板的同名冲突,返回 WARNING 级别的验证问题,并优先使用内联模板。
#### Scenario: 同名冲突时发出警告
- **WHEN** 幻灯片引用的模板名称同时存在于内联模板和模板库中
- **THEN** 系统生成 WARNING 级别的验证问题
- **AND** 错误代码为 `TEMPLATE_NAME_CONFLICT`
- **AND** 错误消息包含"模板名称冲突: '<name>' 同时存在于内联模板和外部模板库"
- **AND** 系统优先使用内联模板
#### Scenario: 同名冲突时优先使用内联模板
- **WHEN** 内联模板和外部模板都定义了 `title-slide`
- **AND** 幻灯片引用 `template: title-slide`
- **THEN** 系统使用内联模板的定义
- **AND** 忽略模板库中的同名模板
#### Scenario: 无冲突时正常加载
- **WHEN** 模板名称仅存在于内联模板或外部模板库中
- **THEN** 系统正常加载,不发出警告
### Requirement: 系统必须支持自定义幻灯片
系统 SHALL 支持不使用模板的自定义幻灯片,以及同时使用模板和自定义元素的混合模式幻灯片。自定义元素的图片相对路径应相对于文档 YAML 所在目录解析。
#### Scenario: 渲染自定义幻灯片
- **WHEN** 幻灯片未指定 `template` 字段,直接包含 `elements` 列表
- **THEN** 系统跳过模板渲染,直接处理元素列表
- **AND** 自定义元素的图片相对路径相对于文档 YAML 所在目录解析
#### Scenario: 自定义幻灯片中直接指定样式
- **WHEN** 自定义幻灯片的元素直接指定 `color: "#4a90e2"`
- **THEN** 系统正确应用该颜色值
#### Scenario: 自定义元素的图片路径解析
- **WHEN** 文档 YAML 位于 `/doc/presentation.yaml`
- **AND** 自定义元素包含图片 `src: "./images/chart.png"`
- **THEN** 系统将路径解析为 `/doc/images/chart.png`
#### Scenario: 自定义幻灯片和模板混合使用
- **WHEN** 演示文稿中部分幻灯片使用模板,部分为自定义
- **THEN** 系统正确处理两种类型的幻灯片
- **AND** 模板元素的图片相对于模板库目录,自定义元素的图片相对于文档目录
#### Scenario: 混合模式幻灯片同时使用模板和自定义元素
- **WHEN** 幻灯片同时指定了 `template` 字段和 `elements` 列表
- **THEN** 系统先渲染模板获取模板元素列表,再追加自定义元素列表,生成最终的元素列表
- **AND** 模板元素的图片已解析为绝对路径(相对于模板库)
- **AND** 自定义元素的图片已解析为绝对路径(相对于文档)
#### Scenario: 混合模式中模板元素在前
- **WHEN** 幻灯片使用混合模式,模板元素和自定义元素位置重叠
- **THEN** 自定义元素在 z 轴上覆盖模板元素(后渲染在上层)
### Requirement: 模板变量解析必须深度递归
系统 SHALL 递归解析模板元素的所有嵌套字段中的变量引用。
#### Scenario: 解析嵌套对象中的变量
- **WHEN** 模板元素定义了 `font: {size: "{font_size}", color: "{text_color}"}`
- **THEN** 系统正确解析嵌套对象中的所有变量引用
#### Scenario: 解析数组中的变量
- **WHEN** 模板元素定义了 `box: ["{left}", 2, 8, 3]`
- **THEN** 系统正确解析数组中的变量引用
#### Scenario: 解析多层嵌套的变量
- **WHEN** 模板包含复杂的嵌套结构,多层使用变量引用
- **THEN** 系统递归解析所有层级的变量,直到无变量引用为止
### Requirement: 幻灯片定义必须支持 enabled 字段
幻灯片定义 SHALL 支持可选的 `enabled` 布尔字段用于控制该幻灯片是否渲染。该字段与模板系统的其他字段template、vars、elements、background独立工作。
#### Scenario: 幻灯片包含 enabled 字段
- **WHEN** 幻灯片定义包含 `enabled: false``enabled: true`
- **THEN** 系统正常加载幻灯片定义,并在渲染时检查该字段
#### Scenario: enabled 字段可选
- **WHEN** 幻灯片定义未包含 `enabled` 字段
- **THEN** 系统默认该幻灯片为启用状态
#### Scenario: enabled 与 template 共存
- **WHEN** 幻灯片同时定义了 `enabled: false``template: title-slide`
- **THEN** 系统跳过该幻灯片,不加载模板
#### Scenario: enabled 与自定义幻灯片共存
- **WHEN** 自定义幻灯片(不使用模板)定义了 `enabled: false`
- **THEN** 系统跳过该幻灯片,不渲染元素列表
### Requirement: 模板与自定义元素必须支持变量共享
系统 SHALL 允许自定义元素访问模板中定义的变量,实现主题色、布局参数等值的统一控制。
#### Scenario: 自定义元素使用模板变量
- **WHEN** 幻灯片使用 `template: content-slide`,提供 `vars: {theme_color: "#3949ab"}`,且自定义元素中定义 `fill: "{theme_color}"`
- **THEN** 系统将自定义元素中的 `{theme_color}` 替换为 `"#3949ab"`
#### Scenario: 自定义元素使用模板默认变量
- **WHEN** 模板定义了 `default: "#3949ab"``theme_color` 变量,幻灯片未提供该变量值,且自定义元素引用 `{theme_color}`
- **THEN** 系统使用模板的默认值 `"#3949ab"` 进行替换
#### Scenario: 自定义元素引用未定义变量时报错
- **WHEN** 自定义元素引用了 `{undefined_var}`,且该变量未在模板 vars 中定义,也未由幻灯片提供
- **THEN** 系统抛出错误,指出未定义的变量
### Requirement: 元素合并必须采用追加策略
系统 SHALL 使用简单追加策略合并模板元素和自定义元素,保持渲染顺序和 z 轴层级。
#### Scenario: 模板元素和自定义元素合并顺序
- **WHEN** 模板渲染后产生 2 个元素,幻灯片自定义元素列表包含 3 个元素
- **THEN** 最终元素列表包含 5 个元素顺序为模板元素1、模板元素2、自定义元素1、自定义元素2、自定义元素3
#### Scenario: 空自定义元素列表
- **WHEN** 幻灯片指定 `template``elements: []`(空数组)
- **THEN** 最终元素列表仅包含模板元素,等同于不指定 `elements` 字段
#### Scenario: 模板条件渲染后的元素合并
- **WHEN** 模板包含 3 个元素,其中 1 个因 `visible` 条件为假被过滤,幻灯片包含 2 个自定义元素
- **THEN** 最终元素列表包含 4 个元素:模板的 2 个可见元素,加上幻灯片的 2 个自定义元素
### Requirement: 混合模式必须保持向后兼容
系统 SHALL 在不使用混合模式时,保持与现有版本完全一致的行为。
#### Scenario: 仅使用模板时不指定 elements
- **WHEN** 幻灯片仅指定 `template` 字段,不包含 `elements` 字段
- **THEN** 系统表现与现有版本完全一致,仅渲染模板元素
#### Scenario: 仅使用自定义元素时不指定 template
- **WHEN** 幻灯片仅指定 `elements` 字段,不包含 `template` 字段
- **THEN** 系统表现与现有版本完全一致,仅渲染自定义元素
#### Scenario: 既不使用模板也不使用自定义元素
- **WHEN** 幻灯片既不指定 `template` 也不指定 `elements`
- **THEN** 系统生成空幻灯片(仅包含背景设置)
### Requirement: 混合模式必须支持内联模板
系统 SHALL 在混合模式中支持内联模板与外部模板库,功能保持一致。
#### Scenario: 内联模板与自定义元素混合使用
- **WHEN** 幻灯片引用内联模板(在 YAML 文件的 `templates` 字段中定义)
- **AND** 同时包含 `elements` 列表
- **THEN** 系统正确渲染内联模板元素
- **AND** 追加自定义元素
- **AND** 图片路径正确解析(内联模板相对于文档目录)
#### Scenario: 外部模板与自定义元素混合使用
- **WHEN** 幻灯片引用外部模板(从 `--template` 指定的模板库加载)
- **AND** 同时包含 `elements` 列表
- **THEN** 系统正确加载外部模板
- **AND** 渲染模板元素
- **AND** 追加自定义元素
- **AND** 图片路径正确解析(外部模板相对于模板库目录)
#### Scenario: 内联和外部模板在同一演示文稿中混合使用
- **WHEN** 演示文稿同时定义了内联模板和使用外部模板
- **AND** 部分幻灯片使用混合模式
- **THEN** 系统正确处理所有组合情况