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

155
core/condition_evaluator.py Normal file
View File

@@ -0,0 +1,155 @@
"""
条件评估器模块
提供基于 simpleeval 的安全条件表达式评估能力
"""
from simpleeval import (
EvalWithCompoundTypes,
NameNotDefined,
FunctionNotDefined,
FeatureNotAvailable,
AttributeDoesNotExist
)
from loaders.yaml_loader import YAMLError
class ConditionEvaluator:
"""条件表达式评估器
使用 simpleeval 库安全地评估条件表达式,支持:
- 比较运算符:==, !=, >, <, >=, <=
- 逻辑运算符and, or, not
- 成员测试in, not in
- 列表/元组字面量
- 数学运算:+, -, *, /, %, **
- 内置函数int, float, str, len, bool, abs, min, max
"""
# 表达式最大长度限制(防止过于复杂的表达式)
MAX_EXPRESSION_LENGTH = 500
def __init__(self):
"""初始化条件评估器"""
pass
def _get_evaluator(self, vars_values):
"""
获取配置好的 simpleeval 实例
Args:
vars_values: 变量值字典
Returns:
EvalWithCompoundTypes 实例
"""
# 每次评估都创建新实例,避免状态污染
evaluator = EvalWithCompoundTypes(names=vars_values)
# 添加额外的安全函数到白名单
evaluator.functions.update({
'len': len,
'bool': bool,
'abs': abs,
'min': min,
'max': max,
})
return evaluator
def _extract_expression(self, condition):
"""
从条件字符串中提取表达式
Args:
condition: 条件字符串,如 "{count > 0}"
Returns:
str: 提取的表达式,如 "count > 0"
"""
# 去除首尾的 { } 和空白
expr = condition.strip()
if expr.startswith('{') and expr.endswith('}'):
expr = expr[1:-1].strip()
return expr
def evaluate_condition(self, condition, vars_values):
"""
评估条件表达式
Args:
condition: 条件字符串,如 "{count > 0 and status == 'active'}"
vars_values: 变量值字典
Returns:
bool: 条件是否满足
Raises:
YAMLError: 表达式评估失败
"""
# 提取 {expression} 中的表达式
expr = self._extract_expression(condition)
# 验证表达式长度
if len(expr) > self.MAX_EXPRESSION_LENGTH:
raise YAMLError(
f"条件表达式过长({len(expr)} 字符,最大 {self.MAX_EXPRESSION_LENGTH}: {condition}"
)
# 评估表达式
try:
evaluator = self._get_evaluator(vars_values)
result = evaluator.eval(expr)
return bool(result)
except NameNotDefined as e:
# 变量未定义
var_name = str(e).split("'")[1] if "'" in str(e) else "unknown"
raise YAMLError(
f"条件表达式中的变量未定义: {var_name}\n"
f"表达式: {condition}\n"
f"可用变量: {', '.join(vars_values.keys())}"
)
except FunctionNotDefined as e:
# 函数未定义
func_name = str(e).split("'")[1] if "'" in str(e) else "unknown"
raise YAMLError(
f"条件表达式中使用了不支持的函数: {func_name}\n"
f"表达式: {condition}\n"
f"提示: 仅支持 int(), float(), str(), len(), bool(), abs(), min(), max() 等基本函数"
)
except FeatureNotAvailable as e:
# 功能不可用
raise YAMLError(
f"条件表达式使用了不支持的语法特性\n"
f"表达式: {condition}\n"
f"错误: {str(e)}\n"
f"提示: 不支持函数定义、导入模块、属性访问等高级特性"
)
except AttributeDoesNotExist as e:
# 属性不存在(属性访问被禁止)
raise YAMLError(
f"条件表达式使用了不支持的语法特性\n"
f"表达式: {condition}\n"
f"错误: {str(e)}\n"
f"提示: 不支持属性访问,请使用字典访问方式"
)
except SyntaxError as e:
# 语法错误
raise YAMLError(
f"条件表达式语法错误\n"
f"表达式: {condition}\n"
f"错误: {str(e)}"
)
except Exception as e:
# 其他错误
raise YAMLError(
f"条件表达式评估失败\n"
f"表达式: {condition}\n"
f"错误: {type(e).__name__}: {str(e)}"
)

View File

@@ -7,6 +7,7 @@
import re
from pathlib import Path
from loaders.yaml_loader import YAMLError, load_yaml_file, validate_template_yaml
from core.condition_evaluator import ConditionEvaluator
class Template:
@@ -20,6 +21,9 @@ class Template:
template_file: 模板名称(纯文件名,不含路径)
templates_dir: 模板文件目录
"""
# 初始化条件评估器
self._condition_evaluator = ConditionEvaluator()
# 检查是否提供了 templates_dir
if templates_dir is None:
raise YAMLError(
@@ -71,15 +75,18 @@ class Template:
"""
obj = cls.__new__(cls)
obj.data = template_data
# 初始化条件评估器
obj._condition_evaluator = ConditionEvaluator()
# 解析变量定义
obj.vars_def = {}
for var in template_data.get('vars', []):
obj.vars_def[var['name']] = var
# 元素列表
obj.elements = template_data.get('elements', [])
return obj
def _external_template_exists(self, template_name):
@@ -153,27 +160,17 @@ class Template:
def evaluate_condition(self, condition, vars_values):
"""
评估条件表达式(简单的存在性检查)
评估条件表达式
Args:
condition: 条件字符串,如 "{subtitle != ''}"
condition: 条件字符串,如 "{count > 0 and status == 'active'}"
vars_values: 变量值字典
Returns:
bool: 条件是否满足
"""
# 简单实现:检查变量是否非空
# 匹配 {var_name != ''} 或 {var_name != ""}
pattern = r'\{(\w+)\s*!=\s*[\'\"]{2}\}'
match = re.match(pattern, condition)
if match:
var_name = match.group(1)
value = vars_values.get(var_name, '')
return value != ''
# 默认返回 True
return True
# 委托给条件评估器
return self._condition_evaluator.evaluate_condition(condition, vars_values)
def render(self, vars_values):
"""