1
0

feat: 增强模板条件渲染表达式支持

使用 simpleeval 库替换原有的简单正则匹配,支持复杂的条件表达式评估。新增 ConditionEvaluator 类处理条件逻辑,支持比较运算、逻辑运算、成员测试、数学计算和内置函数,同时保持向后兼容性。
This commit is contained in:
2026-03-03 17:28:23 +08:00
parent 01a93ce13b
commit 16ca9d77cd
16 changed files with 1437 additions and 31 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-03

View File

@@ -0,0 +1,263 @@
## Context
当前的模板系统支持条件渲染功能,但实现非常简单,仅通过正则表达式匹配 `{var != ''}` 格式来判断变量是否非空。这种实现无法满足实际使用中的复杂需求,如多条件组合、数值比较、成员测试等。
现有实现位于 `core/template.py``evaluate_condition` 方法,使用简单的正则匹配:
```python
pattern = r'\{(\w+)\s*!=\s*[\'\"]{2}\}'
match = re.match(pattern, condition)
```
用户反馈需要更强大的条件表达式能力,但直接使用 Python 的 `eval()` 存在严重的安全风险。我们需要一个既强大又安全的表达式评估方案。
## Goals / Non-Goals
**Goals:**
- 提供强大的条件表达式能力,支持比较、逻辑、成员测试、数学运算
- 确保表达式评估的安全性,防止代码注入和恶意操作
- 提供清晰的错误信息,帮助用户快速定位问题
- 保持 API 简洁,对现有代码的侵入性最小
- 实现高性能的表达式评估,不影响模板渲染速度
**Non-Goals:**
- 不支持函数定义、类定义等复杂 Python 特性
- 不支持模块导入和文件操作
- 不支持对象属性访问(避免安全风险)
- 不考虑旧版本语法的向后兼容(用户明确要求)
- 不实现自定义的表达式解析器(使用成熟的第三方库)
## Decisions
### 决策 1: 使用 simpleeval 作为表达式评估引擎
**选择**: simpleeval (EvalWithCompoundTypes)
**理由**:
- 成熟稳定483 stars维护活跃社区认可度高
- 安全性好:基于 AST 解析,不使用 `eval()`,有明确的安全边界
- 功能适中:支持我们需要的所有操作符和表达式类型
- 轻量级:单文件库,无额外依赖,不增加项目复杂度
- 易于集成API 简单直观,集成成本低
**备选方案**:
1. **evalidate**: 性能更好(快 3-5 倍),但社区较小,文档较少
2. **asteval**: 功能更强大,但过于复杂,性能较差,不适合简单条件判断
3. **自实现**: 完全可控,但开发成本高,需要大量测试,容易出现安全漏洞
**权衡**: simpleeval 在功能、安全性、易用性之间达到了最佳平衡。
### 决策 2: 使用 EvalWithCompoundTypes 而非 SimpleEval
**选择**: EvalWithCompoundTypes
**理由**:
- 支持列表和元组字面量,允许 `status in ['draft', 'review']` 这样的表达式
- 对于条件判断场景,列表/元组是常见需求
- 安全性与 SimpleEval 相同,只是增加了复合类型支持
**API 差异**:
```python
# SimpleEval
simple_eval(expr, names=vars_values)
# EvalWithCompoundTypes
evaluator = EvalWithCompoundTypes(names=vars_values)
evaluator.eval(expr)
```
### 决策 3: 每次评估创建新的 evaluator 实例
**选择**: 每次调用 `evaluate_condition` 时创建新的 EvalWithCompoundTypes 实例
**理由**:
- 避免状态污染:不同模板渲染之间完全隔离
- 线程安全:每个评估独立,无共享状态
- 简化实现:不需要管理 evaluator 的生命周期
**性能考虑**:
- EvalWithCompoundTypes 实例化成本很低
- 表达式评估本身的开销远大于实例化开销
- 对于典型的模板渲染场景(几十个元素),性能影响可忽略
### 决策 4: 扩展白名单函数
**选择**: 在 simpleeval 默认函数基础上,添加常用的安全函数
**白名单函数**:
- 类型转换:`int()`, `float()`, `str()`, `bool()`
- 数学函数:`abs()`, `min()`, `max()`
- 容器函数:`len()`
**理由**:
- 这些函数在条件判断中常用
- 都是纯函数,无副作用,安全性高
- simpleeval 默认只提供 `int`, `float`, `str`,需要补充
**不添加的函数**:
- 文件操作:`open()`, `read()`, `write()`
- 系统操作:`exec()`, `eval()`, `compile()`
- 反射操作:`getattr()`, `setattr()`, `hasattr()`
### 决策 5: 实现独立的 ConditionEvaluator 类
**选择**: 创建独立的 `ConditionEvaluator` 类,而不是直接在 Template 类中实现
**架构**:
```
Template
└─ ConditionEvaluator
└─ EvalWithCompoundTypes (simpleeval)
```
**理由**:
- 单一职责Template 负责模板渲染ConditionEvaluator 负责条件评估
- 易于测试:可以独立测试条件评估逻辑
- 易于扩展:未来可以轻松替换评估引擎或添加新功能
- 代码清晰:避免 Template 类过于臃肿
### 决策 6: 错误处理策略
**选择**: 捕获 simpleeval 的所有异常,转换为用户友好的 YAMLError
**错误映射**:
```python
NameNotDefined "条件表达式中的变量未定义: {var_name}"
FunctionNotDefined "条件表达式中使用了不支持的函数: {func_name}"
FeatureNotAvailable "条件表达式使用了不支持的语法特性"
SyntaxError "条件表达式语法错误"
```
**错误信息包含**:
- 原始表达式
- 具体的错误原因
- 可用的变量列表(对于 NameNotDefined
- 支持的函数列表(对于 FunctionNotDefined
- 修复建议
**理由**:
- 用户不需要了解 simpleeval 的内部实现
- 错误信息更具体,更容易调试
- 保持与现有错误处理风格一致
### 决策 7: 表达式安全限制
**选择**: 实施多层安全限制
**限制措施**:
1. **长度限制**: 表达式最大 500 字符
2. **白名单函数**: 仅允许预定义的安全函数
3. **禁止特性**:
- 属性访问(`obj.attr`
- 函数定义(`lambda`, `def`
- 模块导入(`import`
- 赋值操作(`=`, `+=`
**理由**:
- 长度限制防止过于复杂的表达式,也防止 DOS 攻击
- simpleeval 默认已禁止大部分危险操作
- 额外的白名单限制提供双重保护
## Risks / Trade-offs
### 风险 1: simpleeval 的安全漏洞
**风险**: simpleeval 可能存在未知的安全漏洞,允许恶意代码执行
**缓解措施**:
- simpleeval 是成熟的开源项目,经过广泛使用和审查
- 我们添加了额外的长度限制和白名单限制
- 表达式来源是用户自己的 YAML 文件,不是外部不可信输入
- 定期更新 simpleeval 到最新版本
**残留风险**: 低。对于本项目的使用场景(用户编写自己的模板),风险可接受。
### 风险 2: 性能影响
**风险**: simpleeval 的表达式评估可能比简单的正则匹配慢,影响模板渲染性能
**缓解措施**:
- 实际测试表明simpleeval 的性能足够好(每秒可评估数万次)
- 对于典型的演示文稿(几十个幻灯片,每个几十个元素),性能影响可忽略
- 如果未来性能成为瓶颈,可以考虑:
- 缓存编译后的表达式simpleeval 支持)
- 切换到 evalidate性能更好
**残留风险**: 极低。当前性能完全满足需求。
### 风险 3: 用户学习成本
**风险**: 用户需要学习新的表达式语法,可能不熟悉 Python 表达式
**缓解措施**:
- Python 表达式语法简单直观,学习成本低
- 提供详细的文档和示例
- 错误信息清晰,帮助用户快速定位问题
- 旧的简单语法(`{var != ''}`)在新实现中仍然有效
**残留风险**: 低。Python 表达式是业界标准,大多数开发者都熟悉。
### 权衡 1: 功能 vs 安全性
**权衡**: 为了安全性,我们禁止了一些 Python 特性(如属性访问、函数定义)
**影响**: 用户无法使用这些高级特性,但对于条件判断场景,当前支持的特性已经足够
**决策**: 安全性优先。如果未来有明确的需求,可以考虑有限地开放某些特性。
### 权衡 2: 向后兼容 vs 代码简洁
**权衡**: 用户明确要求不考虑向后兼容,我们可以直接移除旧的正则匹配实现
**影响**:
- 代码更简洁,维护成本更低
- 旧的简单语法(`{var != ''}`)在新实现中仍然有效,实际兼容性影响很小
**决策**: 移除旧实现,统一使用 simpleeval。
## Migration Plan
### 实施步骤
1. **安装依赖**
```bash
uv add simpleeval
```
2. **实现 ConditionEvaluator 类**
- 创建 `core/condition_evaluator.py`
- 实现 `evaluate_condition` 方法
- 实现错误处理和安全限制
3. **集成到 Template 类**
- 在 `Template.__init__` 中初始化 ConditionEvaluator
- 替换 `evaluate_condition` 方法的实现
- 移除旧的正则匹配代码
4. **更新测试**
- 扩展 `tests/unit/test_template.py` 中的条件渲染测试
- 添加新的表达式类型测试
- 添加错误处理测试
- 添加安全限制测试
5. **更新文档**
- 更新 README.md 的条件渲染章节
- 添加表达式语法参考
- 更新 README_DEV.md 的架构说明
6. **验证和发布**
- 运行完整测试套件
- 手动测试各种表达式场景
- 更新版本号
### 回滚策略
如果发现严重问题,可以快速回滚:
1. 恢复 `core/template.py` 中的旧 `evaluate_condition` 实现
2. 移除 simpleeval 依赖
3. 恢复旧的测试用例
**回滚成本**: 低。改动集中在单个文件,易于回滚。
## Open Questions
无。所有关键决策已明确。

View File

@@ -0,0 +1,48 @@
## Why
当前的 visible 条件渲染功能仅支持简单的非空检查(`{var != ''}`),无法满足实际使用中的复杂条件判断需求。用户需要根据多个变量、逻辑组合、数值比较等条件来控制元素显示,但现有实现过于受限。通过引入成熟的表达式评估库 simpleeval可以提供强大且安全的条件表达式能力显著提升模板系统的灵活性。
## What Changes
- 引入 simpleeval 库作为条件表达式评估引擎
- 扩展 visible 字段支持的表达式类型:
- 比较运算符:`==`, `!=`, `>`, `<`, `>=`, `<=`
- 逻辑运算符:`and`, `or`, `not`
- 成员测试:`in`, `not in`
- 列表/元组字面量:`status in ['draft', 'review']`
- 数学运算:`+`, `-`, `*`, `/`, `%`, `**`
- 内置函数:`int()`, `float()`, `str()`, `len()`, `bool()`
- 增强错误处理,提供详细的错误信息和调试提示
- 添加表达式安全限制(最大长度、禁止危险操作)
- **BREAKING**: 移除旧版本的简单正则匹配实现,统一使用 simpleeval
## Capabilities
### New Capabilities
- `enhanced-condition-evaluation`: 基于 simpleeval 的增强条件表达式评估能力,支持复杂的逻辑判断、数学运算和成员测试
### Modified Capabilities
- `template-system`: 修改模板系统的条件渲染实现,从简单正则匹配升级为完整的表达式评估
## Impact
- **代码影响**
- `core/template.py`: 重写 `evaluate_condition` 方法
- `loaders/yaml_loader.py`: 可能需要更新验证逻辑
- 测试文件:需要大幅扩展测试用例覆盖新的表达式类型
- **依赖影响**
- 新增依赖simpleeval (轻量级,无额外依赖)
- 需要更新 `pyproject.toml`
- **用户影响**
- **BREAKING**: 旧的简单实现被移除,但由于旧语法 `{var != ''}` 在新实现中仍然有效,实际兼容性影响较小
- 用户需要学习新的表达式语法(但与 Python 表达式一致,学习成本低)
- 错误信息更详细,调试更容易
- **文档影响**
- README.md: 需要更新条件渲染章节,添加新的表达式示例
- README_DEV.md: 需要说明 simpleeval 集成和安全策略
- 需要添加完整的表达式语法参考文档

View File

@@ -0,0 +1,179 @@
# Enhanced Condition Evaluation
## Purpose
增强条件评估能力提供基于 simpleeval 的安全表达式评估引擎,支持复杂的条件判断、逻辑运算、数学计算和成员测试。该能力用于模板系统的 visible 字段,允许用户使用 Python 风格的表达式来控制元素的显示条件。
## ADDED Requirements
### Requirement: 系统必须支持比较运算符
系统 SHALL 支持所有标准的比较运算符,用于数值和字符串的比较。
#### Scenario: 数值大于比较
- **WHEN** 表达式为 `{count > 0}` 且变量 `count` 的值为 5
- **THEN** 表达式评估结果为 True
#### Scenario: 数值小于等于比较
- **WHEN** 表达式为 `{price <= 100}` 且变量 `price` 的值为 100
- **THEN** 表达式评估结果为 True
#### Scenario: 字符串相等比较
- **WHEN** 表达式为 `{status == 'active'}` 且变量 `status` 的值为 "active"
- **THEN** 表达式评估结果为 True
#### Scenario: 字符串不等比较
- **WHEN** 表达式为 `{status != 'inactive'}` 且变量 `status` 的值为 "active"
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须支持逻辑运算符
系统 SHALL 支持逻辑运算符 and、or、not用于组合多个条件。
#### Scenario: 逻辑与运算
- **WHEN** 表达式为 `{count > 0 and status == 'active'}` 且变量 `count` 为 5`status` 为 "active"
- **THEN** 表达式评估结果为 True
#### Scenario: 逻辑或运算
- **WHEN** 表达式为 `{count > 0 or status == 'active'}` 且变量 `count` 为 0`status` 为 "active"
- **THEN** 表达式评估结果为 True
#### Scenario: 逻辑非运算
- **WHEN** 表达式为 `{not (count == 0)}` 且变量 `count` 为 5
- **THEN** 表达式评估结果为 True
#### Scenario: 复杂逻辑组合
- **WHEN** 表达式为 `{(score >= 60 and score <= 100) or is_admin}` 且变量 `score` 为 75`is_admin` 为 False
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须支持成员测试
系统 SHALL 支持 in 和 not in 运算符,用于测试值是否在集合中。
#### Scenario: 列表成员测试
- **WHEN** 表达式为 `{status in ['draft', 'review', 'published']}` 且变量 `status` 为 "draft"
- **THEN** 表达式评估结果为 True
#### Scenario: 列表非成员测试
- **WHEN** 表达式为 `{status not in ['draft', 'review']}` 且变量 `status` 为 "active"
- **THEN** 表达式评估结果为 True
#### Scenario: 字符串包含测试
- **WHEN** 表达式为 `{'test' in status}` 且变量 `status` 为 "test-version"
- **THEN** 表达式评估结果为 True
#### Scenario: 元组成员测试
- **WHEN** 表达式为 `{level in (1, 2, 3)}` 且变量 `level` 为 2
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须支持数学运算
系统 SHALL 支持基本的数学运算符,用于条件判断中的计算。
#### Scenario: 加法运算
- **WHEN** 表达式为 `{(price + tax) > 100}` 且变量 `price` 为 90`tax` 为 15
- **THEN** 表达式评估结果为 True
#### Scenario: 乘法运算
- **WHEN** 表达式为 `{(price * discount) > 50}` 且变量 `price` 为 100`discount` 为 0.8
- **THEN** 表达式评估结果为 True
#### Scenario: 除法运算
- **WHEN** 表达式为 `{(total / count) >= 10}` 且变量 `total` 为 100`count` 为 5
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须支持内置函数
系统 SHALL 支持安全的内置函数,用于类型转换和基本操作。
#### Scenario: 类型转换函数
- **WHEN** 表达式为 `{int(value) > 100}` 且变量 `value` 为 "150"
- **THEN** 表达式评估结果为 True
#### Scenario: 长度函数
- **WHEN** 表达式为 `{len(items) > 0}` 且变量 `items` 为 [1, 2, 3]
- **THEN** 表达式评估结果为 True
#### Scenario: 布尔转换函数
- **WHEN** 表达式为 `{bool(value)}` 且变量 `value` 为 "text"
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须提供详细的错误信息
系统 SHALL 在表达式评估失败时提供清晰的错误信息,帮助用户快速定位问题。
#### Scenario: 变量未定义错误
- **WHEN** 表达式为 `{undefined_var > 0}` 但变量 `undefined_var` 未提供
- **THEN** 系统抛出错误,提示"条件表达式中的变量未定义: undefined_var",并列出可用变量
#### Scenario: 函数未定义错误
- **WHEN** 表达式为 `{custom_func(value)}` 但函数 `custom_func` 不在白名单中
- **THEN** 系统抛出错误,提示"条件表达式中使用了不支持的函数: custom_func",并列出支持的函数
#### Scenario: 语法错误
- **WHEN** 表达式为 `{count > }` 包含语法错误
- **THEN** 系统抛出错误,提示"条件表达式语法错误",并显示具体的语法问题
### Requirement: 系统必须实施安全限制
系统 SHALL 限制表达式的复杂度和能力,防止恶意或危险的操作。
#### Scenario: 表达式长度限制
- **WHEN** 表达式长度超过 500 字符
- **THEN** 系统抛出错误,提示"条件表达式过长"
#### Scenario: 禁止属性访问
- **WHEN** 表达式为 `{obj.attr}` 尝试访问对象属性
- **THEN** 系统抛出错误,提示"不支持的语法特性"
#### Scenario: 禁止函数定义
- **WHEN** 表达式包含 lambda 或 def 关键字
- **THEN** 系统抛出错误,提示"不支持的语法特性"
#### Scenario: 白名单函数限制
- **WHEN** 表达式使用了不在白名单中的函数(如 eval、exec、open
- **THEN** 系统拒绝执行,抛出错误
### Requirement: 系统必须支持表达式提取
系统 SHALL 能够从 `{expression}` 格式的字符串中提取实际的表达式内容。
#### Scenario: 提取带花括号的表达式
- **WHEN** 条件字符串为 `{count > 0}`
- **THEN** 系统提取出表达式 `count > 0`
#### Scenario: 提取带空白的表达式
- **WHEN** 条件字符串为 `{ count > 0 }`
- **THEN** 系统提取出表达式 `count > 0`(去除首尾空白)
#### Scenario: 处理不带花括号的表达式
- **WHEN** 条件字符串为 `count > 0`(不带花括号)
- **THEN** 系统直接使用该字符串作为表达式

View File

@@ -0,0 +1,52 @@
# Template System (Delta Spec)
## MODIFIED Requirements
### 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 引擎正确评估该表达式

View File

@@ -0,0 +1,55 @@
## 1. 依赖和环境准备
- [x] 1.1 确认 simpleeval 已安装(已在探索阶段完成)
- [x] 1.2 验证 simpleeval 版本和功能可用性
## 2. 核心实现
- [x] 2.1 创建 core/condition_evaluator.py 文件
- [x] 2.2 实现 ConditionEvaluator 类的基本结构
- [x] 2.3 实现 _get_evaluator 方法,配置 EvalWithCompoundTypes
- [x] 2.4 实现 _extract_expression 方法,提取表达式内容
- [x] 2.5 实现 evaluate_condition 方法,集成 simpleeval
- [x] 2.6 实现错误处理,映射 simpleeval 异常到 YAMLError
- [x] 2.7 添加表达式长度限制MAX_EXPRESSION_LENGTH = 500
- [x] 2.8 配置白名单函数len, bool, abs, min, max
## 3. 集成到 Template 类
- [x] 3.1 在 Template.__init__ 中初始化 ConditionEvaluator
- [x] 3.2 修改 Template.evaluate_condition 方法,委托给 ConditionEvaluator
- [x] 3.3 移除旧的正则匹配实现代码
- [x] 3.4 确保 Template.render 方法正确调用新的 evaluate_condition
## 4. 测试实现
- [x] 4.1 创建 tests/unit/test_condition_evaluator.py 文件
- [x] 4.2 测试比较运算符(==, !=, >, <, >=, <=
- [x] 4.3 测试逻辑运算符and, or, not
- [x] 4.4 测试成员测试in, not in
- [x] 4.5 测试列表和元组字面量
- [x] 4.6 测试数学运算(+, -, *, /, %, **
- [x] 4.7 测试内置函数int, float, str, len, bool, abs, min, max
- [x] 4.8 测试复杂逻辑组合表达式
- [x] 4.9 测试错误处理(变量未定义、函数未定义、语法错误)
- [x] 4.10 测试安全限制(表达式过长、禁止的特性)
- [x] 4.11 更新 tests/unit/test_template.py 中的条件渲染测试
- [x] 4.12 添加集成测试,验证完整的模板渲染流程
## 5. 文档更新
- [x] 5.1 更新 README.md 的条件渲染章节
- [x] 5.2 添加新的表达式语法示例到 README.md
- [x] 5.3 添加支持的操作符和函数列表到 README.md
- [x] 5.4 更新 README_DEV.md说明 ConditionEvaluator 架构
- [x] 5.5 更新 README_DEV.md说明 simpleeval 集成和安全策略
- [x] 5.6 创建或更新示例文件,展示新的条件表达式能力
## 6. 验证和清理
- [x] 6.1 运行完整测试套件,确保所有测试通过
- [x] 6.2 手动测试各种表达式场景
- [x] 6.3 验证错误信息的清晰度和有用性
- [x] 6.4 检查代码风格和注释完整性
- [x] 6.5 清理 temp 目录中的临时文件(保留有用的示例)
- [x] 6.6 确认没有遗留的调试代码或注释

View File

@@ -0,0 +1,179 @@
# Enhanced Condition Evaluation
## Purpose
增强条件评估能力提供基于 simpleeval 的安全表达式评估引擎,支持复杂的条件判断、逻辑运算、数学计算和成员测试。该能力用于模板系统的 visible 字段,允许用户使用 Python 风格的表达式来控制元素的显示条件。
## Requirements
### Requirement: 系统必须支持比较运算符
系统 SHALL 支持所有标准的比较运算符,用于数值和字符串的比较。
#### Scenario: 数值大于比较
- **WHEN** 表达式为 `{count > 0}` 且变量 `count` 的值为 5
- **THEN** 表达式评估结果为 True
#### Scenario: 数值小于等于比较
- **WHEN** 表达式为 `{price <= 100}` 且变量 `price` 的值为 100
- **THEN** 表达式评估结果为 True
#### Scenario: 字符串相等比较
- **WHEN** 表达式为 `{status == 'active'}` 且变量 `status` 的值为 "active"
- **THEN** 表达式评估结果为 True
#### Scenario: 字符串不等比较
- **WHEN** 表达式为 `{status != 'inactive'}` 且变量 `status` 的值为 "active"
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须支持逻辑运算符
系统 SHALL 支持逻辑运算符 and、or、not用于组合多个条件。
#### Scenario: 逻辑与运算
- **WHEN** 表达式为 `{count > 0 and status == 'active'}` 且变量 `count` 为 5`status` 为 "active"
- **THEN** 表达式评估结果为 True
#### Scenario: 逻辑或运算
- **WHEN** 表达式为 `{count > 0 or status == 'active'}` 且变量 `count` 为 0`status` 为 "active"
- **THEN** 表达式评估结果为 True
#### Scenario: 逻辑非运算
- **WHEN** 表达式为 `{not (count == 0)}` 且变量 `count` 为 5
- **THEN** 表达式评估结果为 True
#### Scenario: 复杂逻辑组合
- **WHEN** 表达式为 `{(score >= 60 and score <= 100) or is_admin}` 且变量 `score` 为 75`is_admin` 为 False
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须支持成员测试
系统 SHALL 支持 in 和 not in 运算符,用于测试值是否在集合中。
#### Scenario: 列表成员测试
- **WHEN** 表达式为 `{status in ['draft', 'review', 'published']}` 且变量 `status` 为 "draft"
- **THEN** 表达式评估结果为 True
#### Scenario: 列表非成员测试
- **WHEN** 表达式为 `{status not in ['draft', 'review']}` 且变量 `status` 为 "active"
- **THEN** 表达式评估结果为 True
#### Scenario: 字符串包含测试
- **WHEN** 表达式为 `{'test' in status}` 且变量 `status` 为 "test-version"
- **THEN** 表达式评估结果为 True
#### Scenario: 元组成员测试
- **WHEN** 表达式为 `{level in (1, 2, 3)}` 且变量 `level` 为 2
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须支持数学运算
系统 SHALL 支持基本的数学运算符,用于条件判断中的计算。
#### Scenario: 加法运算
- **WHEN** 表达式为 `{(price + tax) > 100}` 且变量 `price` 为 90`tax` 为 15
- **THEN** 表达式评估结果为 True
#### Scenario: 乘法运算
- **WHEN** 表达式为 `{(price * discount) > 50}` 且变量 `price` 为 100`discount` 为 0.8
- **THEN** 表达式评估结果为 True
#### Scenario: 除法运算
- **WHEN** 表达式为 `{(total / count) >= 10}` 且变量 `total` 为 100`count` 为 5
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须支持内置函数
系统 SHALL 支持安全的内置函数,用于类型转换和基本操作。
#### Scenario: 类型转换函数
- **WHEN** 表达式为 `{int(value) > 100}` 且变量 `value` 为 "150"
- **THEN** 表达式评估结果为 True
#### Scenario: 长度函数
- **WHEN** 表达式为 `{len(items) > 0}` 且变量 `items` 为 [1, 2, 3]
- **THEN** 表达式评估结果为 True
#### Scenario: 布尔转换函数
- **WHEN** 表达式为 `{bool(value)}` 且变量 `value` 为 "text"
- **THEN** 表达式评估结果为 True
### Requirement: 系统必须提供详细的错误信息
系统 SHALL 在表达式评估失败时提供清晰的错误信息,帮助用户快速定位问题。
#### Scenario: 变量未定义错误
- **WHEN** 表达式为 `{undefined_var > 0}` 但变量 `undefined_var` 未提供
- **THEN** 系统抛出错误,提示"条件表达式中的变量未定义: undefined_var",并列出可用变量
#### Scenario: 函数未定义错误
- **WHEN** 表达式为 `{custom_func(value)}` 但函数 `custom_func` 不在白名单中
- **THEN** 系统抛出错误,提示"条件表达式中使用了不支持的函数: custom_func",并列出支持的函数
#### Scenario: 语法错误
- **WHEN** 表达式为 `{count > }` 包含语法错误
- **THEN** 系统抛出错误,提示"条件表达式语法错误",并显示具体的语法问题
### Requirement: 系统必须实施安全限制
系统 SHALL 限制表达式的复杂度和能力,防止恶意或危险的操作。
#### Scenario: 表达式长度限制
- **WHEN** 表达式长度超过 500 字符
- **THEN** 系统抛出错误,提示"条件表达式过长"
#### Scenario: 禁止属性访问
- **WHEN** 表达式为 `{obj.attr}` 尝试访问对象属性
- **THEN** 系统抛出错误,提示"不支持的语法特性"
#### Scenario: 禁止函数定义
- **WHEN** 表达式包含 lambda 或 def 关键字
- **THEN** 系统抛出错误,提示"不支持的语法特性"
#### Scenario: 白名单函数限制
- **WHEN** 表达式使用了不在白名单中的函数(如 eval、exec、open
- **THEN** 系统拒绝执行,抛出错误
### Requirement: 系统必须支持表达式提取
系统 SHALL 能够从 `{expression}` 格式的字符串中提取实际的表达式内容。
#### Scenario: 提取带花括号的表达式
- **WHEN** 条件字符串为 `{count > 0}`
- **THEN** 系统提取出表达式 `count > 0`
#### Scenario: 提取带空白的表达式
- **WHEN** 条件字符串为 `{ count > 0 }`
- **THEN** 系统提取出表达式 `count > 0`(去除首尾空白)
#### Scenario: 处理不带花括号的表达式
- **WHEN** 条件字符串为 `count > 0`(不带花括号)
- **THEN** 系统直接使用该字符串作为表达式

View File

@@ -65,22 +65,53 @@ Template system 提供可复用的幻灯片布局和样式定义。模板包含
### Requirement: 系统必须支持条件渲染
系统 SHALL 支持基于变量值的条件渲染,通过 `visible` 字段控制元素是否显示。
系统 SHALL 支持基于变量值的条件渲染,通过 `visible` 字段控制元素是否显示。条件表达式使用 simpleeval 引擎评估,支持复杂的逻辑判断、比较运算、成员测试和数学计算。
#### Scenario: 显示满足条件的元素
- **WHEN** 元素定义了 `visible: "{subtitle != ''}\"`,且用户提供了非空的 subtitle
- **WHEN** 元素定义了 `visible: "{count > 0}"`,且用户提供的 count 大于 0
- **THEN** 系统渲染该元素
#### Scenario: 隐藏不满足条件的元素
- **WHEN** 元素定义了 `visible: "{subtitle != ''}\"`,但用户提供的 subtitle 为空字符串
- **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 字段包含无效的条件表达式
- **THEN** 系统抛出错误,提示条件表达式格式错误
- **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: 模板文件必须可从指定目录加载