1618 lines
66 KiB
Python
1618 lines
66 KiB
Python
from __future__ import annotations
|
|
import logging
|
|
import xml.etree.ElementTree as ET
|
|
from typing import Dict, Callable, Any, Optional
|
|
import os
|
|
from enum import Enum, auto
|
|
from equipment_calculation.expressioncalculator import ExpressionCalculator
|
|
import copy
|
|
|
|
|
|
# 配置logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
handlers=[logging.FileHandler("bcl_calculator.log"), logging.StreamHandler()],
|
|
)
|
|
|
|
|
|
class BCLVariantType(Enum):
|
|
TEXT = "文本"
|
|
FLOAT = "浮点数"
|
|
INTEGER = "整形"
|
|
BOOLEAN = "布尔类型"
|
|
DATASOURCE = "数据源"
|
|
EXPRESSION = "表达式"
|
|
# NULL = "NULL"
|
|
|
|
|
|
class BCLOperatorType(Enum):
|
|
"""
|
|
BCL运算符类型枚举
|
|
"""
|
|
|
|
ADD = "+" # 加
|
|
SUBTRACT = "-" # 减
|
|
MULTIPLY = "*" # 乘
|
|
DIVIDE = "/" # 除
|
|
AND = "&&" # 与
|
|
OR = "||" # 或
|
|
NOT = "!" # 非
|
|
EQUAL = "==" # 等于
|
|
NOT_EQUAL = "!=" # 不等于
|
|
GREATER = ">"
|
|
GREATER_EQUAL = ">="
|
|
LESS = "<"
|
|
LESS_EQUAL = "<="
|
|
UNKNOWN = "unknown" # 未知运算符
|
|
|
|
|
|
def str_to_operator(op_str: str) -> BCLOperatorType:
|
|
"""
|
|
将字符串转换为BCLOperatorType枚举
|
|
:param op_str: 运算符字符串
|
|
:return: 对应的BCLOperatorType枚举值
|
|
"""
|
|
op_map = {
|
|
">": BCLOperatorType.GREATER,
|
|
">=": BCLOperatorType.GREATER_EQUAL,
|
|
"<": BCLOperatorType.LESS,
|
|
"<=": BCLOperatorType.LESS_EQUAL,
|
|
"+": BCLOperatorType.ADD,
|
|
"-": BCLOperatorType.SUBTRACT,
|
|
"*": BCLOperatorType.MULTIPLY,
|
|
"/": BCLOperatorType.DIVIDE,
|
|
"&&": BCLOperatorType.AND,
|
|
"||": BCLOperatorType.OR,
|
|
"!": BCLOperatorType.NOT,
|
|
"==": BCLOperatorType.EQUAL,
|
|
"!=": BCLOperatorType.NOT_EQUAL,
|
|
"加": BCLOperatorType.ADD,
|
|
"减": BCLOperatorType.SUBTRACT,
|
|
"乘": BCLOperatorType.MULTIPLY,
|
|
"除": BCLOperatorType.DIVIDE,
|
|
"与": BCLOperatorType.AND,
|
|
"或": BCLOperatorType.OR,
|
|
"非": BCLOperatorType.NOT,
|
|
"等于": BCLOperatorType.EQUAL,
|
|
"不等于": BCLOperatorType.NOT_EQUAL,
|
|
}
|
|
|
|
if op_str is None:
|
|
return BCLOperatorType.UNKNOWN
|
|
|
|
return op_map.get(op_str.strip(), BCLOperatorType.UNKNOWN)
|
|
|
|
|
|
class BCLVariant:
|
|
def _setobject(self, var_type: BCLVariantType, value):
|
|
self.type = var_type
|
|
self.value = value
|
|
|
|
def __init__(self, value: Any):
|
|
"""
|
|
根据value的类型自动确定变量类型
|
|
:param value: 变量值,可以是str, ET.Element, float, int, bool或list类型
|
|
"""
|
|
if isinstance(value, BCLVariant):
|
|
return self._setobject(value.type, value.value)
|
|
if isinstance(value, str):
|
|
return self._setobject(BCLVariantType.TEXT, value)
|
|
elif isinstance(value, ET.Element):
|
|
return self._setobject(BCLVariantType.EXPRESSION, value)
|
|
elif isinstance(value, list):
|
|
return self._setobject(BCLVariantType.DATASOURCE, value)
|
|
elif isinstance(value, bool):
|
|
return self._setobject(BCLVariantType.BOOLEAN, value)
|
|
elif isinstance(value, float):
|
|
return self._setobject(BCLVariantType.FLOAT, value)
|
|
elif isinstance(value, int):
|
|
return self._setobject(BCLVariantType.INTEGER, value)
|
|
elif value == None:
|
|
# return self._setobject(BCLVariantType.NULL, "")
|
|
return self._setobject(BCLVariantType.TEXT, "")
|
|
else:
|
|
raise ValueError(f"不支持的类型: {type(value)}")
|
|
|
|
def get_value(self) -> Any:
|
|
"""
|
|
获取变量值
|
|
:return: 变量值,转换为浮点数
|
|
"""
|
|
try:
|
|
if self.type == BCLVariantType.TEXT:
|
|
return self.value
|
|
elif self.type == BCLVariantType.DATASOURCE:
|
|
return self.value.get_value()
|
|
elif self.type == BCLVariantType.FLOAT:
|
|
return float(self.value)
|
|
elif self.type == BCLVariantType.INTEGER:
|
|
return int(self.value)
|
|
elif self.type == BCLVariantType.EXPRESSION:
|
|
return self.value
|
|
elif self.type == BCLVariantType.BOOLEAN:
|
|
return bool(self.value)
|
|
else:
|
|
raise ValueError(f"未知的变量类型: {self.type}")
|
|
except (ValueError, TypeError) as e:
|
|
raise ValueError(f"获取变量的值发生错误: {str(e)}\n")
|
|
|
|
def __str__(self) -> str:
|
|
"""
|
|
返回变量的字符串表示
|
|
:return: 变量的字符串表示
|
|
"""
|
|
return f"Variant({self.type}, {self.value})"
|
|
|
|
|
|
# BCL上下文类(用于供计算过程中从该类中获取变量值)
|
|
class BCLContext:
|
|
def __init__(self, valueDict: Dict[str, Any] = None, **kwargs):
|
|
self.variables = {} # 变量字典,键为变量名,值为BCLVariant对象
|
|
self.params = {} # 参数字典,键为字符串,单独存储参数
|
|
|
|
if valueDict:
|
|
for key, value in valueDict.items():
|
|
if isinstance(value, BCLVariant):
|
|
self.variables[key] = value
|
|
else:
|
|
self.variables[key] = BCLVariant(value.__dict__)
|
|
|
|
def _get_variable_value(self, var_name: str) -> BCLVariant:
|
|
"""
|
|
获取变量值
|
|
:param var_name: 变量名,格式为'对象.属性'
|
|
:return: 变量值,如果变量不存在则返回None,请勿抛出异常
|
|
"""
|
|
return self.variables.get(var_name)
|
|
|
|
def get_variable_value(self, var_name: str) -> BCLVariant:
|
|
"""
|
|
获取变量值
|
|
:param var_name: 变量名,格式为'对象.属性'
|
|
:return: 变量值,如果变量不存在则返回None,请勿抛出异常
|
|
"""
|
|
var = self._get_variable_value(var_name)
|
|
if var:
|
|
return var
|
|
else:
|
|
return None
|
|
|
|
def set_param(self, key: str, value):
|
|
"""
|
|
设置参数到参数字典
|
|
:param key: 参数键,必须为字符串
|
|
:param value: 参数值
|
|
"""
|
|
if not isinstance(key, str):
|
|
raise TypeError("参数键必须是字符串类型")
|
|
self.params[key] = value
|
|
|
|
def get_param(self, key: str, default=None):
|
|
"""
|
|
从参数字典获取参数
|
|
:param key: 参数键,必须为字符串
|
|
:param default: 默认值,如果参数不存在则返回
|
|
:return: 参数值或默认值
|
|
"""
|
|
if not isinstance(key, str):
|
|
raise TypeError("参数键必须是字符串类型")
|
|
return self.params.get(key, default)
|
|
|
|
|
|
# 链式BCL上下文类,用于将两个上下文对象穿成一个链表,从而实现依次从两个上下文中获取变量值(该类是抽象类,不支持直接使用)
|
|
class BCLPrevContext(BCLContext):
|
|
def __init__(self, prevContext: BCLContext = None):
|
|
super().__init__()
|
|
self._prevContext = prevContext
|
|
|
|
def _get_variable_value(self, var_name: str) -> BCLVariant:
|
|
"""
|
|
获取变量值,支持点号分隔的变量名查询
|
|
:param var_name: 变量名,可以是简单变量名或'对象.属性'格式
|
|
:return: 找到的变量或None
|
|
"""
|
|
result = super()._get_variable_value(var_name)
|
|
if result == None and self._prevContext:
|
|
# 如果当前对象没有找到变量,尝试在前一个上下文中查找
|
|
try:
|
|
result = self._prevContext._get_variable_value(var_name)
|
|
except ValueError:
|
|
pass
|
|
|
|
return result
|
|
|
|
def set_param(self, key: str, value):
|
|
"""
|
|
设置参数到参数字典
|
|
:param key: 参数键,必须为字符串
|
|
:param value: 参数值
|
|
"""
|
|
if not isinstance(key, str):
|
|
raise TypeError("参数键必须是字符串类型")
|
|
if self._prevContext:
|
|
self._prevContext.set_param(key, value)
|
|
else:
|
|
super().set_param(key, value)
|
|
|
|
def get_param(self, key: str, default=None):
|
|
"""
|
|
从参数字典获取参数
|
|
:param key: 参数键,必须为字符串
|
|
:param default: 默认值,如果参数不存在则返回
|
|
:return: 参数值或默认值
|
|
"""
|
|
if not isinstance(key, str):
|
|
raise TypeError("参数键必须是字符串类型")
|
|
result = super().get_param(key, default)
|
|
if result == None and self._prevContext:
|
|
result = self._prevContext.get_param(key, default)
|
|
return result
|
|
|
|
|
|
# 前缀BCL上下文类,用于将一个上下文对象与一个前缀绑定,从而实现从该上下文中获取变量值
|
|
class BCLPrefixContext(BCLContext):
|
|
def __init__(self, prefix: str, childContext: BCLContext):
|
|
self._childContext = childContext
|
|
self._prefix = prefix
|
|
|
|
def __init__(self, prefix: str, valueDict: Dict[str, Any] = None):
|
|
self._childContext = BCLContext(valueDict)
|
|
self._prefix = prefix
|
|
|
|
def _get_variable_value(self, var_name: str) -> BCLVariant:
|
|
"""
|
|
获取变量值,支持点号分隔的变量名查询
|
|
:param var_name: 变量名,可以是简单变量名或'对象.属性'格式
|
|
:return: 找到的变量或None
|
|
"""
|
|
result = None
|
|
# 检查是否是点号分隔的两段式变量名
|
|
if "." in var_name:
|
|
parts = var_name.split(".", maxsplit=1)
|
|
if len(parts) >= 2 and parts[0] == self._prefix:
|
|
# 如果是当前数据对象的属性,则查询对应的变量
|
|
try:
|
|
result = self._childContext._get_variable_value(parts[1])
|
|
except ValueError:
|
|
pass
|
|
return result
|
|
|
|
|
|
# 前缀链式BCL上下文类,用于将一个上下文对象与一个前缀绑定,从而实现从该上下文中获取变量值
|
|
class BCLPrefixPrevContext(BCLPrefixContext, BCLPrevContext):
|
|
def __init__(self, prefix: str, childContext: BCLContext, prevContext: BCLContext = None):
|
|
BCLPrefixContext.__init__(self, prefix, childContext)
|
|
BCLPrevContext.__init__(self, prevContext)
|
|
|
|
def __init__(self, prefix: str, valueDict: Dict[str, Any], prevContext: BCLContext = None):
|
|
BCLPrefixContext.__init__(self, prefix, valueDict)
|
|
BCLPrevContext.__init__(self, prevContext)
|
|
|
|
def _get_variable_value(self, var_name: str) -> BCLVariant:
|
|
"""
|
|
获取变量值,支持点号分隔的变量名查询
|
|
:param var_name: 变量名,可以是简单变量名或'对象.属性'格式
|
|
:return: 找到的变量或None
|
|
"""
|
|
result = BCLPrefixContext._get_variable_value(self, var_name)
|
|
if result == None:
|
|
# 如果当前对象没有找到变量,尝试在前一个上下文中查找
|
|
try:
|
|
result = BCLPrevContext._get_variable_value(self, var_name)
|
|
except ValueError:
|
|
pass
|
|
|
|
return result
|
|
|
|
|
|
class BCLDataSourceItem:
|
|
|
|
def __init__(self, valueDict: Any, parent: BCLDataSourceItem = None, data_name: str = ""):
|
|
self.variables = {} # 变量字典,键为变量名,值为BCLVariant对象
|
|
self._parent = parent
|
|
self._childs = []
|
|
self._dataname: str = data_name
|
|
|
|
# 处理值字典
|
|
if valueDict:
|
|
items = valueDict if isinstance(valueDict, Dict) else valueDict.__dict__
|
|
|
|
# 将所有属性添加到变量字典中
|
|
for key, value in items.items():
|
|
if isinstance(value, BCLVariant):
|
|
self.variables[key] = value
|
|
else:
|
|
self.variables[key] = BCLVariant(value)
|
|
|
|
# 检查是否有子节点列表(children 或其他名称)
|
|
if "children" in items:
|
|
child_nodes = items["children"]
|
|
if isinstance(child_nodes, list):
|
|
# 为每个子节点创建 BCLDataSourceItem 对象
|
|
self._childs = [BCLDataSourceItem(child, self) for child in child_nodes]
|
|
|
|
def get_dataname(self) -> str:
|
|
return self._dataname
|
|
|
|
def get_value(self, var_name: str) -> BCLVariant:
|
|
|
|
return self.variables.get(var_name)
|
|
|
|
def get_parent(self):
|
|
if self._parent:
|
|
return self._parent
|
|
else:
|
|
return None
|
|
# return self._parent
|
|
|
|
def get_items(self) -> list:
|
|
return self._childs
|
|
|
|
def set_parent(self, parent: BCLDataSourceItem):
|
|
self._parent = parent
|
|
|
|
def set_childs(self, childs: list):
|
|
self._childs = childs
|
|
|
|
|
|
class BCLDataSourceContext(BCLPrevContext):
|
|
def __init__(self, data_source: list[BCLDataSourceItem], prevContext: BCLContext = None):
|
|
super().__init__(prevContext)
|
|
self._data_source = data_source
|
|
|
|
def _get_variable_value(self, var_name: str) -> BCLVariant:
|
|
result = None
|
|
if var_name == "source":
|
|
return BCLVariant(self._data_source)
|
|
else:
|
|
return super()._get_variable_value(var_name)
|
|
|
|
|
|
class BCLCalcItemContext(BCLPrevContext):
|
|
def __init__(self, data_name: str, data_source: list[BCLDataSourceItem], prevContext: BCLContext = None):
|
|
super().__init__(prevContext)
|
|
self._index = 0
|
|
self._data_name = data_name
|
|
self._data_source = data_source
|
|
|
|
def _get_variable_value(self, var_name: str) -> BCLVariant:
|
|
"""
|
|
获取变量值,支持点号分隔的变量名查询
|
|
:param var_name: 变量名,可以是简单变量名或'对象.属性'格式
|
|
:return: 找到的变量或None
|
|
"""
|
|
result = None
|
|
# 检查是否是点号分隔的两段式变量名
|
|
if "." in var_name:
|
|
parts = var_name.split(".", maxsplit=1)
|
|
prefix_name = parts[0]
|
|
# 如果是当前数据对象的属性,则查询对应的变量
|
|
try:
|
|
if prefix_name == "parent":
|
|
value_name = parts[1]
|
|
child_pairt = parts[1]
|
|
item = self._data_source[self._index - 1].get_parent()
|
|
# 处理可能的多级属性访问
|
|
while "." in child_pairt and item:
|
|
child_parts = child_pairt.split(".", maxsplit=1)
|
|
if child_parts[0] == "parent":
|
|
item = item.get_parent()
|
|
value_name = child_parts[1]
|
|
child_pairt = child_parts[1]
|
|
else:
|
|
break
|
|
|
|
if item:
|
|
result = BCLVariant(item.get_value(value_name))
|
|
else:
|
|
result = BCLVariant("")
|
|
|
|
elif prefix_name == self._data_name or prefix_name == "curnode":
|
|
result = BCLVariant(self._data_source[self._index - 1].get_value(parts[1]))
|
|
elif (
|
|
self._data_source[self._index - 1].get_parent()
|
|
and prefix_name == self._data_source[self._index - 1].get_parent().get_dataname()
|
|
):
|
|
result = BCLVariant(self._data_source[self._index - 1].get_parent().get_value(parts[1]))
|
|
except ValueError:
|
|
pass
|
|
|
|
if result == None:
|
|
try:
|
|
if var_name == "curnode":
|
|
result = BCLVariant(self._data_source[self._index - 1])
|
|
elif var_name == "parent":
|
|
result = BCLVariant(self._data_source[self._index - 1].get_parent())
|
|
elif var_name == "items":
|
|
result = BCLVariant(self._data_source[self._index - 1].get_items())
|
|
elif (
|
|
self._data_source[self._index - 1].get_parent()
|
|
and var_name == self._data_source[self._index - 1].get_parent().get_dataname()
|
|
):
|
|
result = BCLVariant(self._data_source[self._index - 1].get_parent())
|
|
else:
|
|
result = super()._get_variable_value(var_name)
|
|
except ValueError:
|
|
pass
|
|
|
|
return result
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
if self._index >= len(self._data_source):
|
|
raise StopIteration
|
|
value = self._data_source[self._index]
|
|
self._index += 1
|
|
return value
|
|
|
|
|
|
# 定义test函数
|
|
def test_func(funcname: str, claculate, context: BCLContext, *args):
|
|
logging.debug(f"call function : {funcname}")
|
|
return 0
|
|
|
|
|
|
def round_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 2:
|
|
raise ValueError(f"{funcname}函数需要2个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查第一个参数类型(浮点数或表达式)
|
|
if not isinstance(args[0], BCLVariant) or args[0].type not in (BCLVariantType.FLOAT, BCLVariantType.EXPRESSION):
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是浮点数或表达式类型")
|
|
|
|
# 检查第二个参数类型(整数或表达式)
|
|
if not isinstance(args[1], BCLVariant) or args[1].type not in (BCLVariantType.INTEGER, BCLVariantType.EXPRESSION):
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是整数或表达式类型")
|
|
|
|
# 获取参数值
|
|
value = args[0].value
|
|
decimal_places = args[1].value
|
|
|
|
# print(f"call function : {funcname}\n")
|
|
logging.debug(f"四舍五入值: {value}, 小数位数: {decimal_places}")
|
|
|
|
# 执行四舍五入计算
|
|
return BCLVariant(round(float(value), int(decimal_places)))
|
|
|
|
|
|
def calc_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 1:
|
|
raise ValueError(f"{funcname}函数需要1个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型(表达式)
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.EXPRESSION:
|
|
raise ValueError(f"{funcname}函数的参数必须是表达式类型")
|
|
|
|
# 获取参数值
|
|
expression = args[0].value
|
|
|
|
# print(f"call function : {funcname}\n")
|
|
logging.debug(f"计算表达式: {expression}")
|
|
|
|
# 执行表达式计算并返回结果
|
|
return BCLVariant(expression)
|
|
|
|
|
|
def sort_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 4:
|
|
raise ValueError(f"{funcname}函数需要4个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.DATASOURCE:
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是数据源类型")
|
|
if not isinstance(args[1], BCLVariant) or args[1].type != BCLVariantType.TEXT:
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是文本类型")
|
|
if not isinstance(args[2], BCLVariant) or args[2].type != BCLVariantType.EXPRESSION:
|
|
raise ValueError(f"{funcname}函数的第三个参数必须是表达式类型")
|
|
if not isinstance(args[3], BCLVariant) or args[3].type != BCLVariantType.STRING:
|
|
raise ValueError(f"{funcname}函数的第四个参数必须是字符串类型")
|
|
|
|
# 获取参数值
|
|
data_source = args[0].value
|
|
text_param = args[1].value
|
|
expression = args[2].value
|
|
string_param = args[3].value
|
|
|
|
logging.debug(f"call function : {funcname}\n")
|
|
logging.debug(f"数据源: {data_source}")
|
|
logging.debug(f"文本参数: {text_param}")
|
|
logging.debug(f"表达式: {expression}")
|
|
logging.debug(f"字符串参数: {string_param}")
|
|
|
|
temp_calculator = copy.copy(claculate)
|
|
# 计算排序键并存储原始索引
|
|
sorted_items = []
|
|
temp_context = BCLCalcItemContext(text_param, data_source, context)
|
|
for index, item in enumerate(temp_context):
|
|
try:
|
|
# 计算排序表达式的值
|
|
result = temp_calculator.evaluate_node(expression, temp_context)
|
|
if not isinstance(result, BCLVariant):
|
|
raise TypeError("排序表达式计算结果必须是BCLVariant类型")
|
|
if result.type not in (BCLVariantType.INTEGER, BCLVariantType.FLOAT):
|
|
raise TypeError(f"排序表达式计算结果类型必须是数字,实际是{result.type}")
|
|
sorted_items.append((float(result.value if result.value else "0"), index, item))
|
|
except Exception as e:
|
|
logging.error(f"计算排序键时出错: {e}", exc_info=True)
|
|
# 出错项放在最后
|
|
sorted_items.append((float("inf"), index, item))
|
|
|
|
# 根据排序键和方向排序
|
|
reverse_order = string_param.lower() == "desc"
|
|
sorted_items.sort(key=lambda x: (x[0], x[1]), reverse=reverse_order)
|
|
|
|
# 提取排序后的数据源
|
|
sorted_data = [item for (key, index, item) in sorted_items]
|
|
|
|
# 返回排序后的结果
|
|
return BCLVariant(sorted_data)
|
|
|
|
|
|
def isempty_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 1:
|
|
raise ValueError(f"{funcname}函数需要1个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.DATASOURCE:
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是数据源类型")
|
|
|
|
# 获取参数值
|
|
data_source = args[0].value
|
|
|
|
logging.debug(f"call function : {funcname}\n")
|
|
logging.debug(f"数据源: {data_source}\n")
|
|
|
|
# 检查数据源是否为空
|
|
is_empty = len(data_source) == 0
|
|
# 返回检查结果
|
|
return BCLVariant(is_empty)
|
|
|
|
|
|
def setparam_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 2:
|
|
raise ValueError(f"{funcname}函数需要2个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查第一个参数类型 (文本或表达式)
|
|
if not isinstance(args[0], BCLVariant) or args[0].type not in (BCLVariantType.TEXT, BCLVariantType.EXPRESSION):
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是文本或表达式类型")
|
|
|
|
# 检查第二个参数类型 (BCLVariant)
|
|
if not isinstance(args[1], BCLVariant):
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是BCLVariant类型")
|
|
|
|
# 获取参数值
|
|
param_name_var = args[0]
|
|
param_value = args[1]
|
|
|
|
if param_name_var.type == BCLVariantType.EXPRESSION:
|
|
# 计算参数名 (如果是表达式则求值)
|
|
calculator = BCLCalculator()
|
|
param_name_result = calculator._evaluate_node(param_name_var.value, context)
|
|
if param_name_result.type != BCLVariantType.TEXT:
|
|
raise TypeError(f"{funcname}函数的第一个参数表达式必须返回文本类型")
|
|
param_name = param_name_result.value
|
|
else:
|
|
param_name = param_name_var.value
|
|
|
|
if param_value.type == BCLVariantType.EXPRESSION:
|
|
# 计算参数名 (如果是表达式则求值)
|
|
calculator = BCLCalculator()
|
|
param_value_result = calculator._evaluate_node(param_value.value, context)
|
|
if param_value_result.type != BCLVariantType.TEXT:
|
|
raise TypeError(f"{funcname}函数的第二个参数表达式必须返回文本类型")
|
|
param_value = param_value_result.value
|
|
|
|
# 设置参数到上下文
|
|
context.set_param(param_name, param_value)
|
|
|
|
# 返回设置的参数值
|
|
return param_value
|
|
|
|
|
|
def getparam_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 1:
|
|
raise ValueError(f"{funcname}函数需要1个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型 (文本或表达式)
|
|
if not isinstance(args[0], BCLVariant) or args[0].type not in (BCLVariantType.TEXT, BCLVariantType.EXPRESSION):
|
|
raise ValueError(f"{funcname}函数的参数必须是文本或表达式类型")
|
|
|
|
# 获取参数值
|
|
param_name_var = args[0]
|
|
|
|
# 计算参数名 (如果是表达式则求值)
|
|
calculator = BCLCalculator()
|
|
if param_name_var.type == BCLVariantType.EXPRESSION:
|
|
param_name_result = calculator._evaluate_node(param_name_var.value, context)
|
|
if param_name_result.type != BCLVariantType.TEXT:
|
|
raise TypeError(f"{funcname}函数的参数表达式必须返回文本类型")
|
|
param_name = param_name_result.value
|
|
else:
|
|
param_name = param_name_var.value
|
|
|
|
# 从上下文获取参数
|
|
param_value = context.get_param(param_name)
|
|
|
|
# 返回参数值,如果不存在则返回NULL类型
|
|
if param_value is None:
|
|
return BCLVariant(None, BCLVariantType.NULL)
|
|
return param_value
|
|
|
|
|
|
def groupby_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 4:
|
|
raise ValueError(f"{funcname}函数需要4个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.DATASOURCE:
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是数据源类型")
|
|
if not isinstance(args[1], BCLVariant) or args[1].type != BCLVariantType.TEXT:
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是文本类型")
|
|
if not isinstance(args[2], BCLVariant) or args[2].type not in (BCLVariantType.TEXT, BCLVariantType.EXPRESSION):
|
|
raise ValueError(f"{funcname}函数的第三个参数必须是文本或表达式类型")
|
|
if not isinstance(args[3], BCLVariant) or args[3].type != BCLVariantType.EXPRESSION:
|
|
raise ValueError(f"{funcname}函数的第四个参数必须是表达式类型")
|
|
|
|
# 获取参数值
|
|
data_source = args[0].value
|
|
text_param = args[1].value
|
|
group_param = args[2].value
|
|
calc_expression = args[3].value
|
|
|
|
logging.debug(f"call function : {funcname}\n")
|
|
logging.debug(f"分组参数: {group_param}")
|
|
logging.debug(f"计算表达式: {calc_expression}")
|
|
|
|
temp_calculator = copy.copy(claculate)
|
|
groups = {}
|
|
|
|
# 遍历数据源进行分组
|
|
temp_context = BCLCalcItemContext(text_param, data_source, context)
|
|
for index, item in enumerate(temp_context):
|
|
try:
|
|
# 根据第三个参数类型获取分组键
|
|
if args[2].type == BCLVariantType.TEXT:
|
|
# 文本类型直接作为键
|
|
group_key = group_param
|
|
else:
|
|
# 表达式类型计算结果作为键
|
|
result = temp_calculator.evaluate_node(group_param, temp_context)
|
|
if not isinstance(result, BCLVariant):
|
|
raise TypeError("分组表达式计算结果必须是BCLVariant类型")
|
|
group_key = str(result.value)
|
|
|
|
# 添加到对应分组
|
|
if group_key not in groups:
|
|
groups[group_key] = []
|
|
groups[group_key].append(item)
|
|
except Exception as e:
|
|
logging.error(f"分组处理出错: {e}", exc_info=True)
|
|
# 错误项放入'error'分组
|
|
if "error" not in groups:
|
|
groups["error"] = []
|
|
groups["error"].append(item)
|
|
|
|
# 对每个分组执行第四个表达式计算
|
|
group_results = {}
|
|
group_context = BCLCalcItemContext(text_param, groups.items(), context)
|
|
for index, (key, items) in enumerate(group_context):
|
|
try:
|
|
# 计算表达式结果
|
|
result = temp_calculator.evaluate_node(calc_expression, group_context)
|
|
group_results[key] = {"items": items, "result": result.value if isinstance(result, BCLVariant) else None}
|
|
except Exception as e:
|
|
logging.error(f"分组计算出错: {e}", exc_info=True)
|
|
group_results[key] = {"items": items, "result": None}
|
|
|
|
# 返回分组计算结果
|
|
return BCLVariant(group_results)
|
|
|
|
|
|
def strfind_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 2:
|
|
raise ValueError(f"{funcname}函数需要2个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.TEXT:
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是文本类型")
|
|
if not isinstance(args[1], BCLVariant) or args[1].type != BCLVariantType.TEXT:
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是文本类型")
|
|
|
|
# 获取参数值
|
|
text1 = args[0].value
|
|
text2 = args[1].value
|
|
|
|
# print(f"call function : {funcname}\n")
|
|
logging.debug(f"文本1: {text1}")
|
|
logging.debug(f"文本2: {text2}")
|
|
|
|
# 查找子字符串位置
|
|
position = text1.find(text2)
|
|
|
|
# 返回查找结果
|
|
return BCLVariant(position)
|
|
|
|
|
|
def in_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 参数数量校验
|
|
if len(args) != 2:
|
|
raise ValueError(f"函数 {funcname} 需要传入2个参数,实际传入了{len(args)}个参数")
|
|
|
|
# 参数类型校验
|
|
param1 = args[0]
|
|
param2 = args[1]
|
|
|
|
# 验证参数1:文本或表达式类型
|
|
if not (isinstance(param1, BCLVariant) and param1.type in (BCLVariantType.TEXT, BCLVariantType.EXPRESSION)):
|
|
raise TypeError(f"函数 {funcname} 的第1个参数必须是文本或表达式类型,实际类型为{type(param1).__name__}")
|
|
|
|
# 验证参数2:文本或表达式类型
|
|
if not (isinstance(param2, BCLVariant) and param2.type in (BCLVariantType.TEXT, BCLVariantType.EXPRESSION)):
|
|
raise TypeError(f"函数 {funcname} 的第2个参数必须是文本或表达式类型,实际类型为{type(param2).__name__}")
|
|
|
|
# 调试信息
|
|
logging.debug(f"调用 {funcname} 函数,参数1: {param1}, 参数2: {param2}")
|
|
|
|
# 创建计算器实例
|
|
temp_calculator = copy.copy(claculate)
|
|
|
|
try:
|
|
# 计算两个表达式的值
|
|
value1 = (
|
|
temp_calculator.calculate_expression(param1.value)
|
|
if param1.type == BCLVariantType.EXPRESSION
|
|
else param1.value
|
|
)
|
|
value2 = (
|
|
temp_calculator.calculate_expression(param2.value)
|
|
if param2.type == BCLVariantType.EXPRESSION
|
|
else param2.value
|
|
)
|
|
|
|
# 判断value1是否在value2中
|
|
result = value1 in value2
|
|
|
|
# 返回布尔类型的BCLVariant对象
|
|
return BCLVariant(result)
|
|
except Exception as e:
|
|
logging.error(f"{funcname} 函数执行错误: {str(e)}", exc_info=True)
|
|
raise
|
|
|
|
|
|
def iif_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 3:
|
|
raise ValueError(f"{funcname}函数需要3个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type not in (BCLVariantType.BOOLEAN, BCLVariantType.EXPRESSION):
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是布尔或表达式类型")
|
|
if not isinstance(args[1], BCLVariant) or args[1].type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.EXPRESSION,
|
|
):
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是表达式类型")
|
|
if not isinstance(args[2], BCLVariant) or args[2].type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.EXPRESSION,
|
|
):
|
|
raise ValueError(f"{funcname}函数的第三个参数必须是表达式类型")
|
|
|
|
# 获取参数值
|
|
condition_param = args[0]
|
|
true_expr = args[1]
|
|
false_expr = args[2]
|
|
|
|
# print(f"call function : {funcname}\n")
|
|
|
|
temp_calculator = copy.copy(claculate)
|
|
|
|
try:
|
|
# 计算条件结果
|
|
if condition_param.type == BCLVariantType.EXPRESSION:
|
|
condition_result = temp_calculator.evaluate_node(condition_param.value, context)
|
|
if condition_result.type != BCLVariantType.BOOLEAN:
|
|
raise TypeError("条件表达式必须返回布尔值")
|
|
condition_value = condition_result.value
|
|
else:
|
|
condition_value = condition_param.value
|
|
|
|
# 根据条件计算结果
|
|
if condition_value:
|
|
if true_expr.type == BCLVariantType.EXPRESSION:
|
|
result = temp_calculator.evaluate_node(true_expr.value, context)
|
|
else:
|
|
result = true_expr
|
|
else:
|
|
if false_expr.type == BCLVariantType.EXPRESSION:
|
|
result = temp_calculator.evaluate_node(false_expr.value, context)
|
|
else:
|
|
result = false_expr
|
|
|
|
return result
|
|
except Exception as e:
|
|
print(f"iif函数计算出错: {e}")
|
|
raise e
|
|
|
|
|
|
def for_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 4:
|
|
raise ValueError(f"{funcname}函数需要4个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.DATASOURCE:
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是数据源类型")
|
|
if not isinstance(args[1], BCLVariant) or args[1].type != BCLVariantType.TEXT:
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是文本类型")
|
|
if not isinstance(args[2], BCLVariant) or args[2].type != BCLVariantType.EXPRESSION:
|
|
raise ValueError(f"{funcname}函数的第三个参数必须是表达式类型")
|
|
if not isinstance(args[3], BCLVariant) or args[3].type != BCLVariantType.EXPRESSION:
|
|
raise ValueError(f"{funcname}函数的第四个参数必须是表达式类型")
|
|
|
|
# 获取参数值
|
|
data_source = args[0].value
|
|
loop_var_name = args[1].value
|
|
expr1 = args[2].value
|
|
expr2 = args[3].value
|
|
|
|
logging.debug(f"call function : {funcname}\n")
|
|
logging.debug(f"数据源: {data_source}\n")
|
|
logging.debug(f"循环变量名: {loop_var_name}")
|
|
logging.debug(f"表达式1: {expr1}")
|
|
logging.debug(f"表达式2: {expr2}")
|
|
|
|
temp_calculator = copy.copy(claculate)
|
|
result = None
|
|
|
|
temp_context = BCLCalcItemContext(loop_var_name, data_source, context)
|
|
for index, item in enumerate(temp_context):
|
|
try:
|
|
# 执行第一个表达式
|
|
result1 = temp_calculator.evaluate_node(expr1, temp_context)
|
|
except Exception as e:
|
|
logging.error(f"循环处理出错: {e}", exc_info=True)
|
|
continue
|
|
|
|
if args[3].type == BCLVariantType.EXPRESSION:
|
|
result = claculate.evaluate_node(expr2, context)
|
|
# 返回循环结果
|
|
return BCLVariant(result)
|
|
|
|
|
|
def group_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 3:
|
|
raise ValueError(f"{funcname}函数需要3个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.DATASOURCE:
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是数据源类型")
|
|
if not isinstance(args[1], BCLVariant) or args[1].type != BCLVariantType.TEXT:
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是文本类型")
|
|
if not isinstance(args[2], BCLVariant) or args[2].type not in (BCLVariantType.TEXT, BCLVariantType.EXPRESSION):
|
|
raise ValueError(f"{funcname}函数的第三个参数必须是文本或表达式类型")
|
|
|
|
# 获取参数值
|
|
data_source = args[0].value
|
|
text_param = args[1].value
|
|
third_param = args[2].value
|
|
|
|
logging.debug(f"call function : {funcname}\n")
|
|
logging.debug(f"数据源: {data_source}\n")
|
|
logging.debug(f"文本参数: {text_param}\n")
|
|
logging.debug(f"第三个参数: {third_param}")
|
|
|
|
temp_calculator = copy.copy(claculate)
|
|
groups = {}
|
|
|
|
# 遍历数据源进行分组
|
|
temp_context = BCLCalcItemContext(text_param, data_source, context)
|
|
for index, item in enumerate(temp_context):
|
|
try:
|
|
# 根据第三个参数类型获取分组键
|
|
if args[2].type == BCLVariantType.TEXT:
|
|
# 文本类型直接作为键
|
|
group_key = third_param
|
|
else:
|
|
result = temp_calculator.evaluate_node(third_param, temp_context)
|
|
if not isinstance(result, BCLVariant):
|
|
raise TypeError("分组表达式计算结果必须是BCLVariant类型")
|
|
group_key = str(result.value)
|
|
|
|
# 添加到对应分组
|
|
if group_key not in groups:
|
|
groups[group_key] = []
|
|
groups[group_key].append(item)
|
|
except Exception as e:
|
|
print(f"分组处理出错: {e}")
|
|
# 错误项放入'error'分组
|
|
if "error" not in groups:
|
|
groups["error"] = []
|
|
groups["error"].append(item)
|
|
|
|
# 返回分组后的结果
|
|
return BCLVariant(groups)
|
|
|
|
|
|
def sum_func(funcname: str, claculate, context: BCLContext, *args):
|
|
# 检查参数数量
|
|
if len(args) != 3:
|
|
raise ValueError(f"{funcname}函数需要3个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.DATASOURCE:
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是数据源类型")
|
|
if not isinstance(args[1], BCLVariant) or args[1].type != BCLVariantType.TEXT:
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是文本类型")
|
|
if not isinstance(args[2], BCLVariant) or args[2].type != BCLVariantType.EXPRESSION:
|
|
raise ValueError(f"{funcname}函数的第三个参数必须是表达式类型")
|
|
|
|
# 获取并存储三个参数
|
|
data_source = args[0].value
|
|
data_name = args[1].value
|
|
expression = args[2].value
|
|
|
|
logging.debug(f"call function : {funcname}\n")
|
|
logging.debug(f"数据源: {data_source}\n")
|
|
logging.debug(f"数据变量名称: {data_name}")
|
|
logging.debug(f"表达式: {expression}\n")
|
|
|
|
temp_calculator = copy.copy(claculate)
|
|
# 遍历数据源并累加结果
|
|
total = 0.0
|
|
temp_context = BCLCalcItemContext(data_name, data_source, context)
|
|
for index, item in enumerate(temp_context):
|
|
try:
|
|
result = temp_calculator.evaluate_node(expression, temp_context)
|
|
# 验证结果类型必须是BCLVariant且类型为数字
|
|
if not isinstance(result, BCLVariant):
|
|
raise TypeError("表达式计算结果必须是BCLVariant类型")
|
|
if result.type not in (BCLVariantType.INTEGER, BCLVariantType.FLOAT):
|
|
raise TypeError(f"表达式计算结果类型必须是数字,实际是{result.type}")
|
|
total += float(result.value)
|
|
except Exception as e:
|
|
logging.error(f"表达式计算错误: {str(e)}", exc_info=True)
|
|
continue
|
|
|
|
return BCLVariant(total)
|
|
|
|
|
|
def filter_func(funcname: str, claculate, context: BCLContext, *args):
|
|
"""
|
|
过滤函数,检查参数并执行过滤操作
|
|
:param funcname: 函数名
|
|
:param args: 参数列表,必须包含3个BCLVariant参数
|
|
:return: 过滤结果
|
|
"""
|
|
# 检查参数数量
|
|
if len(args) != 3:
|
|
raise ValueError(f"{funcname}函数需要3个参数,但提供了{len(args)}个参数")
|
|
|
|
# 检查参数类型
|
|
if not isinstance(args[0], BCLVariant) or args[0].type != BCLVariantType.DATASOURCE:
|
|
raise ValueError(f"{funcname}函数的第一个参数必须是数据源类型")
|
|
if not isinstance(args[1], BCLVariant) or args[1].type != BCLVariantType.TEXT:
|
|
raise ValueError(f"{funcname}函数的第二个参数必须是文本类型")
|
|
if not isinstance(args[2], BCLVariant) or args[2].type != BCLVariantType.EXPRESSION:
|
|
raise ValueError(f"{funcname}函数的第三个参数必须是表达式类型")
|
|
|
|
# 获取并存储三个参数
|
|
data_source = args[0].value
|
|
data_name = args[1].value
|
|
expression = args[2].value
|
|
|
|
logging.debug(f"call function : {funcname}\n")
|
|
logging.debug(f"数据源: {data_source}\n")
|
|
logging.debug(f"数据变量名称: {data_name}\n")
|
|
logging.debug(f"表达式: {expression}\n")
|
|
|
|
temp_calculator = copy.copy(claculate)
|
|
|
|
# 遍历数据源并应用过滤条件
|
|
filtered_results = []
|
|
temp_context = BCLCalcItemContext(data_name, data_source, context)
|
|
for index, item in enumerate(temp_context):
|
|
# 调用计算器的evaluate_node方法计算表达式
|
|
try:
|
|
result = temp_calculator.evaluate_node(expression, temp_context)
|
|
# 验证结果类型必须是BCLVariant且类型为布尔或整数
|
|
if not isinstance(result, BCLVariant):
|
|
raise TypeError("表达式计算结果必须是BCLVariant类型")
|
|
if result.type not in (BCLVariantType.BOOLEAN, BCLVariantType.INTEGER):
|
|
raise TypeError(f"表达式计算结果类型必须是布尔或整数,实际是{result.type}")
|
|
# 判别其值为true才添加到filtered_results
|
|
if result.value:
|
|
filtered_results.append(item)
|
|
except Exception as e:
|
|
logging.error(f"表达式计算错误: {str(e)}", exc_info=True)
|
|
continue
|
|
|
|
return BCLVariant(filtered_results)
|
|
|
|
|
|
def recursion_counter(func):
|
|
def wrapper(self, node, *args, **kwargs):
|
|
if not hasattr(self, "_recursion_depth"):
|
|
self._recursion_depth = 0
|
|
if not hasattr(self, "_total_count"):
|
|
self._total_count = 0
|
|
|
|
self._total_count += 1
|
|
self._recursion_depth += 1
|
|
try:
|
|
result = func(self, node, *args, **kwargs)
|
|
return result
|
|
finally:
|
|
self._recursion_depth -= 1
|
|
|
|
return wrapper
|
|
|
|
|
|
class BCLCalculator:
|
|
def __init__(self):
|
|
self.expressions: Dict[str, ET.Element] = {}
|
|
self.functions: Dict[str, Callable] = {}
|
|
self.last_error: Optional[str] = None
|
|
self.loaded_scripts = [] # 存储已加载的脚本相对路径
|
|
|
|
# 注册sum函数
|
|
self.register_function("sum", sum_func)
|
|
self.register_function("calc", calc_func)
|
|
self.register_function("round", round_func)
|
|
self.register_function("filter", filter_func)
|
|
self.register_function("iif", iif_func)
|
|
self.register_function("sort", sort_func)
|
|
self.register_function("group", group_func)
|
|
self.register_function("groupby", groupby_func)
|
|
self.register_function("for", for_func)
|
|
self.register_function("in", in_func)
|
|
self.register_function("isEmpty", isempty_func)
|
|
self.register_function("setparam", setparam_func)
|
|
self.register_function("getparam", getparam_func)
|
|
self.register_function("strFind", strfind_func)
|
|
|
|
def __copy__(self):
|
|
# 创建新实例,复制基本属性
|
|
new_instance = self.__class__()
|
|
new_instance.loaded_scripts = self.loaded_scripts # 直接引用
|
|
new_instance.expressions = self.expressions # 直接引用
|
|
new_instance.functions = self.functions # 直接引用
|
|
return new_instance
|
|
|
|
def load_scripts_dir(self, dir_path: str) -> bool:
|
|
"""
|
|
加载目录下所有XML脚本文件
|
|
:param dir_path: 包含XML文件的目录路径
|
|
:return: 是否全部加载成功
|
|
"""
|
|
if not os.path.isdir(dir_path):
|
|
self.last_error = f"目录'{dir_path}'不存在或不是目录\n"
|
|
return False
|
|
|
|
success = True
|
|
for filename in os.listdir(dir_path):
|
|
if filename.lower().endswith(".xml"):
|
|
xml_path = os.path.join(dir_path, filename)
|
|
if not self.load_script(xml_path):
|
|
success = False
|
|
|
|
return success
|
|
|
|
def load_script(self, xml_path: str) -> bool:
|
|
"""
|
|
加载XML脚本文件并验证其结构
|
|
:param xml_path: XML文件路径
|
|
:return: 是否加载成功
|
|
"""
|
|
try:
|
|
# 检查文件是否存在
|
|
if not os.path.exists(xml_path):
|
|
self.last_error = f"文件'{xml_path}'不存在\n"
|
|
return False
|
|
|
|
# 读取并清理XML文件内容
|
|
import chardet
|
|
|
|
# 检测文件编码
|
|
with open(xml_path, "rb") as f:
|
|
raw_data = f.read()
|
|
encoding = chardet.detect(raw_data)["encoding"]
|
|
|
|
# 使用检测到的编码读取文件内容,并转换为UTF-8
|
|
with open(xml_path, "r", encoding=encoding, errors="replace") as f:
|
|
content = f.read()
|
|
|
|
# 确保内容是UTF-8编码
|
|
if encoding.lower() != "utf-8":
|
|
content = content.encode("utf-8", errors="replace").decode("utf-8")
|
|
|
|
# 清理重复的Type属性
|
|
content = self._clean_xml_attributes(content)
|
|
|
|
# 解析清理后的XML
|
|
root = ET.fromstring(content)
|
|
|
|
# 记录已加载的脚本相对路径
|
|
relative_path = os.path.relpath(xml_path, start=os.getcwd())
|
|
self.loaded_scripts.append(relative_path)
|
|
|
|
# 验证根节点 - 兼容BclDocument作为根节点或在Document-DataDefs下
|
|
if root.tag == "Document":
|
|
data_defs = root.find("DataDefs")
|
|
if data_defs is not None:
|
|
bcl_doc = data_defs.find("BclDocument")
|
|
if bcl_doc is not None:
|
|
root = bcl_doc
|
|
else:
|
|
self.last_error = "在Document/DataDefs下未找到BclDocument节点\n"
|
|
return False
|
|
else:
|
|
self.last_error = "Document节点缺少DataDefs子节点\n"
|
|
return False
|
|
elif root.tag != "BclDocument":
|
|
self.last_error = "根节点必须是BclDocument或Document\n"
|
|
return True
|
|
|
|
# 验证所有BCLExpression节点
|
|
for expr in root.findall("BCLExpression"):
|
|
if not self._validate_expression_node(expr):
|
|
return False
|
|
|
|
name = expr.get("name")
|
|
self.expressions[name] = expr
|
|
|
|
return True
|
|
except ET.ParseError as e:
|
|
self.last_error = f"XML解析错误: {str(e)}\n"
|
|
return False
|
|
except Exception as e:
|
|
self.last_error = f"加载脚本失败: {str(e)}\n"
|
|
return False
|
|
|
|
def _clean_xml_attributes(self, xml_content: str) -> str:
|
|
"""
|
|
清理XML中的重复属性,保留最后一个出现的属性值
|
|
:param xml_content: XML内容字符串
|
|
:return: 清理后的XML内容
|
|
"""
|
|
# 使用正则表达式匹配并清理重复的Type属性
|
|
import re
|
|
|
|
# xml_content = '''<ELNode type="函数" Type="TextW" text="_过滤取费定额" Type="TextW" valtype="5" Type="Int" />'''
|
|
|
|
# 正则表达式删除所有 Type="*" 属性
|
|
xml_content = re.sub(r'Type="[^"]*"', "", xml_content)
|
|
return xml_content
|
|
|
|
def _validate_expression_node(self, node: ET.Element) -> bool:
|
|
"""
|
|
验证表达式节点结构
|
|
:param node: ELNode节点
|
|
:return: 是否验证通过
|
|
"""
|
|
# 检查必须包含ELNode子节点
|
|
el_node = node.find("ELNode")
|
|
if el_node is None:
|
|
self.last_error = f"表达式节点'{node.get('name')}'缺少ELNode子节点"
|
|
return False
|
|
|
|
# 检查ELNode类型
|
|
if el_node.get("type") != "表达式序列":
|
|
self.last_error = f"表达式节点'{node.get('name')}'的ELNode类型必须是'表达式序列'"
|
|
return False
|
|
|
|
return True
|
|
|
|
def register_function(self, name: str, func: Callable) -> None:
|
|
self.functions[name] = func
|
|
|
|
def calculate(self, expr_name: str, context: BCLContext) -> float:
|
|
self.last_error = None
|
|
if expr_name not in self.expressions:
|
|
self.last_error = f"表达式'{expr_name}'未找到"
|
|
logging.error(f"错误: {self.last_error}")
|
|
return None
|
|
|
|
try:
|
|
expr_node = self.expressions[expr_name]
|
|
result = self._evaluate_node(expr_node, context)
|
|
if result is None:
|
|
logging.error(f"错误: {self.last_error}")
|
|
return None
|
|
|
|
return result.value
|
|
except Exception as e:
|
|
self.last_error = f"计算错误: {str(e)}"
|
|
print(f"错误: {self.last_error}")
|
|
return None
|
|
|
|
def evaluate_node(self, node: ET.Element, context: BCLContext) -> BCLVariant:
|
|
self.last_error = None
|
|
|
|
try:
|
|
result = self._evaluate_node(node, context)
|
|
if result is None:
|
|
logging.error(f"错误: {self.last_error}")
|
|
|
|
return result
|
|
except Exception as e:
|
|
self.last_error = f"计算错误: {str(e)}"
|
|
print(f"错误: {self.last_error}")
|
|
return None
|
|
|
|
def _get_variable_value(self, var_name: str, context: BCLContext) -> BCLVariant:
|
|
try:
|
|
var = context.get_variable_value(var_name)
|
|
if var is not None:
|
|
logging.debug(f"找到变量: {var_name}, 值: {var.value}, 类型: {var.type}")
|
|
return var
|
|
else:
|
|
logging.warning(f"未找到变量: {var_name}")
|
|
raise ValueError(f"变量'{var_name}'不存在")
|
|
except (AttributeError, ValueError) as e:
|
|
raise e
|
|
|
|
@recursion_counter
|
|
def _evaluate_node(self, node: ET.Element, context: BCLContext) -> BCLVariant:
|
|
node_type = node.get("type")
|
|
if node_type is not None:
|
|
node_type = node_type.strip()
|
|
|
|
indent = " " * self._recursion_depth
|
|
if node_type is None:
|
|
logging.debug(f"{indent} {node.tag} {node.get('name')}")
|
|
else:
|
|
logging.debug(f"{indent} {node.tag} {node_type} {node.get('text')}")
|
|
|
|
if node_type == "变量":
|
|
var_name = node.get("text").strip()
|
|
return self._get_variable_value(var_name, context)
|
|
elif node_type == "数值":
|
|
return BCLVariant(float(node.get("text").strip()))
|
|
elif node_type == "字符串":
|
|
return BCLVariant(str(node.get("text") if node.get("text") else "").strip())
|
|
elif node_type == "函数":
|
|
func_name = node.get("text").strip()
|
|
return self._call_function(func_name, node, context)
|
|
elif str_to_operator(node_type) != BCLOperatorType.UNKNOWN:
|
|
return self._evaluate_logical_expression(node, context)
|
|
elif node_type == "条件运算符":
|
|
return self._evaluate_flow_expression(node[0], context)
|
|
elif node_type == "简单计算式":
|
|
return self._evaluate_expression(node, context)
|
|
elif node_type == "表达式序列":
|
|
# 处理表达式序列节点
|
|
result = None
|
|
for child in node:
|
|
result = self._evaluate_node(child, context)
|
|
|
|
return result
|
|
elif node_type == "参数起始符号":
|
|
# 处理参数起始符号节点
|
|
result = None
|
|
for child in node:
|
|
result = self._evaluate_node(child, context)
|
|
|
|
return result
|
|
elif node.tag == "BCLExpression" or node.tag == "LeftNode" or node.tag == "RightNode":
|
|
# 处理BCLExpression节点
|
|
child = node[0]
|
|
if child.tag == "ELNode" and child.get("type") == "表达式序列":
|
|
return self._evaluate_node(node[0], context)
|
|
else:
|
|
logging.warning(f"{indent} 未知的计算节点类型: {node_type}")
|
|
raise ValueError(f"未知的计算节点类型: {node_type}")
|
|
|
|
def _evaluate_logical_expression(self, node: ET.Element, context: BCLContext) -> BCLVariant:
|
|
"""
|
|
计算逻辑表达式
|
|
:param node: XML节点
|
|
:param context: 上下文对象
|
|
:return: 逻辑运算结果
|
|
"""
|
|
node_type = node.get("type").strip()
|
|
operator_type = str_to_operator(node_type)
|
|
|
|
left = self._evaluate_node(node.find("LeftNode"), context)
|
|
if left is None:
|
|
left = BCLVariant(False)
|
|
|
|
if operator_type == BCLOperatorType.NOT:
|
|
if not isinstance(left, BCLVariant):
|
|
raise ValueError(f"NOT运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}")
|
|
if left.type in [BCLVariantType.BOOLEAN, BCLVariantType.INTEGER]:
|
|
return BCLVariant(not bool(left.value))
|
|
|
|
raise ValueError(f"运算符 '{node_type}' 参数类型为 {left.type} ,不支持的\n")
|
|
|
|
right = self._evaluate_node(node.find("RightNode"), context)
|
|
if left is None:
|
|
left = BCLVariant(False)
|
|
|
|
if operator_type == BCLOperatorType.AND:
|
|
if not isinstance(left, BCLVariant) or not isinstance(right, BCLVariant):
|
|
raise ValueError(f"'与'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type in [BCLVariantType.BOOLEAN, BCLVariantType.INTEGER] and right.type in [
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.INTEGER,
|
|
]:
|
|
return BCLVariant(bool(left.value) and bool(right.value))
|
|
|
|
raise ValueError(f"运算符 '{node_type}' 两边参数类型分别为 {left.type} {right.type} ,不支持的\n")
|
|
|
|
elif operator_type == BCLOperatorType.OR:
|
|
if not isinstance(left, BCLVariant) or not isinstance(right, BCLVariant):
|
|
raise ValueError(f"'或'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type in [BCLVariantType.BOOLEAN, BCLVariantType.INTEGER] and right.type in [
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.INTEGER,
|
|
]:
|
|
return BCLVariant(bool(left.value) or bool(right.value))
|
|
|
|
raise ValueError(f"运算符 '{node_type}' 两边参数类型分别为 {left.type} {right.type} ,不支持的\n")
|
|
|
|
elif operator_type == BCLOperatorType.EQUAL:
|
|
if not isinstance(left, BCLVariant) or not isinstance(right, BCLVariant):
|
|
raise ValueError(f"'等于'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
# 类型转换逻辑
|
|
if BCLVariantType.BOOLEAN in [left.type, right.type]:
|
|
left_value = bool(left.value if left.value else "0")
|
|
right_value = bool(right.value if right.value else "0")
|
|
elif BCLVariantType.FLOAT in [left.type, right.type]:
|
|
left_value = float(left.value if left.value else "0")
|
|
right_value = float(right.value if right.value else "0")
|
|
elif BCLVariantType.INTEGER in [left.type, right.type]:
|
|
left_value = float(left.value if left.value else "0")
|
|
right_value = float(right.value if right.value else "0")
|
|
else:
|
|
left_value = left.value
|
|
right_value = right.value
|
|
|
|
return BCLVariant(left_value == right_value)
|
|
|
|
elif operator_type == BCLOperatorType.NOT_EQUAL:
|
|
if not isinstance(left, BCLVariant) or not isinstance(right, BCLVariant):
|
|
raise ValueError(f"'不等于'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
return BCLVariant(left.value != right.value)
|
|
|
|
elif operator_type == BCLOperatorType.GREATER:
|
|
if not isinstance(left, BCLVariant) or not isinstance(right, BCLVariant):
|
|
raise ValueError(f"'>'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
|
|
# 支持 FLOAT 和 INTEGER 类型的比较
|
|
if left.type in [BCLVariantType.FLOAT, BCLVariantType.INTEGER] and right.type in [
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.INTEGER,
|
|
]:
|
|
return BCLVariant(float(left.value if left.value else "0") > float(right.value if right.value else "0"))
|
|
|
|
raise ValueError(f"运算符 '{node_type}' 两边参数类型分别为 {left.type} {right.type} ,不支持的\n")
|
|
|
|
elif operator_type == BCLOperatorType.GREATER_EQUAL:
|
|
if not isinstance(left, BCLVariant) or not isinstance(right, BCLVariant):
|
|
raise ValueError(f"'>='运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type in [BCLVariantType.INTEGER] and right.type in [BCLVariantType.INTEGER]:
|
|
return BCLVariant(
|
|
float(left.value if left.value else "0") >= float(right.value if right.value else "0")
|
|
)
|
|
raise ValueError(f"运算符 '{node_type}' 两边参数类型分别为 {left.type} {right.type} ,不支持的\n")
|
|
|
|
elif operator_type == BCLOperatorType.LESS:
|
|
if not isinstance(left, BCLVariant) or not isinstance(right, BCLVariant):
|
|
raise ValueError(f"'<'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type in [BCLVariantType.INTEGER] and right.type in [BCLVariantType.INTEGER]:
|
|
return BCLVariant(float(left.value if left.value else "0") < float(right.value if right.value else "0"))
|
|
raise ValueError(f"运算符 '{node_type}' 两边参数类型分别为 {left.type} {right.type} ,不支持的\n")
|
|
|
|
elif operator_type == BCLOperatorType.LESS_EQUAL:
|
|
if not isinstance(left, BCLVariant) or not isinstance(right, BCLVariant):
|
|
raise ValueError(f"'<='运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type in [BCLVariantType.INTEGER] and right.type in [BCLVariantType.INTEGER]:
|
|
return BCLVariant(
|
|
float(left.value if left.value else "0") <= float(right.value if right.value else "0")
|
|
)
|
|
raise ValueError(f"运算符 '{node_type}' 两边参数类型分别为 {left.type} {right.type} ,不支持的\n")
|
|
|
|
# 四则运算符处理
|
|
elif operator_type == BCLOperatorType.ADD:
|
|
if not (isinstance(left, BCLVariant) and isinstance(right, BCLVariant)):
|
|
raise ValueError(f"'加'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.TEXT,
|
|
) or right.type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.TEXT,
|
|
):
|
|
raise ValueError(
|
|
f"'加'运算符的操作数类型必须为数值、浮点数、布尔或文本,实际为: {left.type}和{right.type}"
|
|
)
|
|
return BCLVariant(float(left.value if left.value else "0") + float(right.value if right.value else "0"))
|
|
|
|
elif operator_type == BCLOperatorType.SUBTRACT:
|
|
if not (isinstance(left, BCLVariant) and isinstance(right, BCLVariant)):
|
|
raise ValueError(f"'减'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.TEXT,
|
|
) or right.type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.TEXT,
|
|
):
|
|
raise ValueError(
|
|
f"'减'运算符的操作数类型必须为数值、浮点数、布尔或文本,实际为: {left.type}和{right.type}"
|
|
)
|
|
return BCLVariant(float(left.value if left.value else "0") - float(right.value if right.value else "0"))
|
|
|
|
elif operator_type == BCLOperatorType.MULTIPLY:
|
|
if not (isinstance(left, BCLVariant) and isinstance(right, BCLVariant)):
|
|
raise ValueError(f"'乘'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.TEXT,
|
|
) or right.type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.TEXT,
|
|
):
|
|
raise ValueError(
|
|
f"'乘'运算符的操作数类型必须为数值、浮点数、布尔或文本,实际为: {left.type}和{right.type}"
|
|
)
|
|
try:
|
|
left_val = float(left.value) if left.value and str(left.value).strip() not in ("", "None") else 0.0
|
|
right_val = float(right.value) if right.value and str(right.value).strip() not in ("", "None") else 0.0
|
|
result = left_val * right_val
|
|
except (TypeError, ValueError, AttributeError):
|
|
result = 0.0
|
|
|
|
return BCLVariant(result)
|
|
|
|
elif operator_type == BCLOperatorType.DIVIDE:
|
|
if not (isinstance(left, BCLVariant) and isinstance(right, BCLVariant)):
|
|
raise ValueError(f"'除'运算符的操作数必须是BCLVariant类型,实际得到: {type(left)}和{type(right)}")
|
|
if left.type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.TEXT,
|
|
) or right.type not in (
|
|
BCLVariantType.INTEGER,
|
|
BCLVariantType.FLOAT,
|
|
BCLVariantType.BOOLEAN,
|
|
BCLVariantType.TEXT,
|
|
):
|
|
raise ValueError(
|
|
f"'除'运算符的操作数类型必须为数值、浮点数、布尔或文本,实际为: {left.type}和{right.type}"
|
|
)
|
|
if float(right.value) == 0:
|
|
raise ZeroDivisionError("除数不能为零")
|
|
return BCLVariant(float(left.value if left.value else "0") / float(right.value if right.value else "0"))
|
|
|
|
raise ValueError(f"运算符 '{node_type}' 类型不支持的\n")
|
|
|
|
def _evaluate_expression(self, expr_node: ET.Element, context: BCLContext) -> BCLVariant:
|
|
try:
|
|
# 收集所有变量值
|
|
variables = {}
|
|
for var_node in expr_node:
|
|
if var_node.get("type") != "变量":
|
|
continue
|
|
|
|
var_name = var_node.get("text")
|
|
variables[var_name] = self._get_variable_value(var_name, context)
|
|
|
|
# 构建表达式字符串
|
|
expr_str = expr_node.get("text")
|
|
|
|
symbols = {}
|
|
for var_name, var_value in variables.items():
|
|
symbols[var_name] = float("0" if not var_value.get_value() else var_value.get_value())
|
|
|
|
calculator = ExpressionCalculator()
|
|
parse_success = calculator.parse_expression(expr_str)
|
|
if not parse_success:
|
|
logging.error(f"表达式解析失败: {calculator.get_last_error()}")
|
|
raise ValueError(f"表达式解析失败: {calculator.get_last_error()}")
|
|
|
|
# 执行四则运算并返回结果
|
|
result, value = calculator.evaluate(symbols)
|
|
if not result:
|
|
logging.error(f"表达式计算失败: {calculator.get_last_error()}")
|
|
raise ValueError(f"表达式计算失败: {calculator.get_last_error()}")
|
|
|
|
return BCLVariant(value)
|
|
except Exception as e:
|
|
raise e
|
|
|
|
def _call_function(self, func_name: str, node: ET.Element, context: BCLContext) -> BCLVariant:
|
|
|
|
if func_name in self.expressions:
|
|
expr_node = self.expressions[func_name]
|
|
result = self._evaluate_node(expr_node, context)
|
|
return result
|
|
|
|
if func_name not in self.functions:
|
|
raise ValueError(f"函数'{func_name}'未注册\n")
|
|
|
|
args = []
|
|
for child in node:
|
|
if child.get("type") == "参数起始符号":
|
|
current_node = child
|
|
while True:
|
|
# 验证子节点数量必须为1
|
|
if len(current_node) != 1:
|
|
raise ValueError(f"函数'{func_name}'参数节点数量错误,预期1个实际{len(current_node)}个\n")
|
|
child_node = current_node[0]
|
|
# 检查是否存在type属性
|
|
if child_node.get("type") != None:
|
|
break
|
|
# 继续遍历子节点
|
|
current_node = child_node
|
|
args.append(BCLVariant(child_node))
|
|
continue
|
|
|
|
args.append(self._evaluate_node(child, context))
|
|
|
|
return self.functions[func_name](func_name, self, context, *args)
|
|
|
|
def get_last_error(self) -> Optional[str]:
|
|
return self.last_error
|
|
|
|
def _evaluate_flow_expression(self, node: ET.Element, context: BCLContext) -> BCLVariant:
|
|
"""
|
|
处理流程表达式(如ConditionNode/ConListElm/ConNode条件分支结构)
|
|
:param node: ConListElm节点
|
|
:param context: 上下文对象
|
|
:return: 满足条件的ValueNode的计算结果
|
|
"""
|
|
defval = None
|
|
# 只处理当前ConListElm节点,不递归遍历所有ConListElm
|
|
for block in node:
|
|
if block.tag == "ConListElm":
|
|
con_node = None
|
|
value_node = None
|
|
for sub in block:
|
|
if sub.tag == "ConNode":
|
|
con_node = sub
|
|
elif sub.tag == "ValueNode":
|
|
value_node = sub
|
|
if con_node is not None and value_node is not None:
|
|
cond_result = self._evaluate_node(con_node, context)
|
|
if cond_result and bool(cond_result.value):
|
|
return self._evaluate_node(
|
|
value_node if value_node.get("type") != None else value_node[0], context
|
|
)
|
|
elif block.tag == "DefVal":
|
|
defval = block
|
|
|
|
if defval is not None:
|
|
return self._evaluate_node(defval, context)
|
|
else:
|
|
# 没有条件成立,返回0.0
|
|
return BCLVariant(0.0)
|