1
0
Files
PPTX/openspec/changes/archive/2026-03-03-enhance-visible-condition-rendering/design.md
lanyuanxiaoyao 16ca9d77cd feat: 增强模板条件渲染表达式支持
使用 simpleeval 库替换原有的简单正则匹配,支持复杂的条件表达式评估。新增 ConditionEvaluator 类处理条件逻辑,支持比较运算、逻辑运算、成员测试、数学计算和内置函数,同时保持向后兼容性。
2026-03-03 17:28:23 +08:00

264 lines
8.9 KiB
Markdown
Raw 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.
## 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
无。所有关键决策已明确。