主要变更: - 将 templates_dir 参数改为 template_file,支持单个模板库 YAML 文件 - 添加模板库 YAML 验证功能 - 为模板添加 base_dir 支持,正确解析相对路径资源 - 内联模板与外部模板同名时改为警告(内联优先) - 移除模板缓存机制,直接使用模板库字典 - 更新所有相关测试以适配新的模板加载方式 此重构简化了模板管理,使模板资源的路径解析更加清晰明确。
14 KiB
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 错误消息包含"模板名称冲突: '' 同时存在于内联模板和外部模板库"
- 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 系统正确处理所有组合情况