按規則解析字元串中的嵌套函數并實作函數調用
需求
1、按照一定規則解析字元串中的函數表達式,并替換這些表達式。這些函數表達式可能包含其它函數表達式,即支援函數嵌套
2、函數表達式格式:
${ __函數名稱() }、${__函數名稱( 函數參數 )}
注意:
- 函數名稱以
打頭_
- 函數參數之間使用
分隔 形如||
${ __function1( "str_value" || 123456 || 'test' )}
-
之間不能有空格${
- 函數名稱和函數的左括号
之間不能有空隔(
- 函數支援嵌套,形如:
${ __function1( ${__function2()} )}
- 函數參數如果是字元串,需要使用單引号、雙引号引用 形如
,${ __function1( "str_value" || 123)}
${ __function1(key="arg_value")}
${ __function1(key=\'arg_value\')}
- 字元串替換規則:待替換的字元串,僅包含一個函數表達式,不含其它字元,則該字元串被替換為函數傳回值,如果還包含其它字元,或者包含多個函數,則該字元串替換函數表達式之前,會先轉換函數傳回值為字元串,然後替換這些函數表達式為轉換後的函數傳回值
- 函數參數支援python原生函數 形如
${ __function1( set([1,2,3]) )}
解決思路
1、先解析内部函數,再解析其父函數,即從内到外解析
實作方式:查找不包含嵌套函數表達式的函數表達式,先臨時替換為“臨時插件函數表達式” 形如
'@plugin_func_custom_function_name@'
,同時以該值為字典
key
,存儲對應臨時函數表達式,然後再用替換後的字元串去查找不包含嵌套函數表達式的函數表達式,然後再替換字元串,直到找不到為止
2、解析替換後的字元串,擷取“臨時插件函數表達式”,然後執行調用該函數
3、函數參數類型分析
字元串參數要求用 單、雙引号 引用,通過
eval(參數)
轉換,如果轉換成功則用轉換後的,否則用轉換前的
實作代碼
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
import re
# 插件函數樣例
def base64(*args, **kwargs):
print('base64 called')
print('args:', args)
print('kwargs:', kwargs)
return 1
def read_file(*args, **kwargs):
print('fread_file called')
print('args:', args)
print('kwargs:', kwargs)
return ['a', 1]
def generate_num(*args, **kwargs):
print('generate_num called')
print('args:', args)
print('kwargs:', kwargs)
return 899999
PUGIN_FUNC_MAP = {'read_file':read_file, 'base64':base64, 'generate_num':generate_num} # 存放插件函數名稱和對應函數實體的映射
func_map = {} # 存放程式執行過程中,擷取的臨時函數名稱和函數表達式的映射關系
REG_FOR_TEMP_PLUGIN_FUNC = re.compile('@(plugin_func.+?)@', re.DOTALL) # 用于查找臨時插件函數名稱 形如 [''@plugin_func__base64@','@plugin_func__read_file@']
REG_FOR_FUNC_NAME_OF_EXP = re.compile('\${\s*(_.+?)\(', re.DOTALL) # 用于查找函數表達式中的函數名稱
REG_FOR_FUNC_NAME_AND_ARGS = re.compile('\${\s*(_.+?)\((.*?)\)\s*}', re.DOTALL) # 用于查找函數表達式中的函數定義(函數名稱及其參數)
REG_FOR_STRICT_FUNC_EXP = re.compile('\${\s*_.+\(.*?\)\s*}', re.DOTALL) # 用于擷取嚴格函數定義表達式
REG_FOR_KWARG = re.compile('^[^"\']+[^"\']+\s*=\s*.+', re.DOTALL) # 用于比對關鍵詞參數
def _replace_function(string):
'''替換字元串中的插件參數'''
string = string.strip()
func_name_list = REG_FOR_TEMP_PLUGIN_FUNC.findall(string) # 擷取函數名稱清單 形如 [''@plugin_func__base64@','@plugin_func__read_file@']
if len(func_name_list) == 1 and string == '@%s@' % func_name_list[0]: # 整個字元串就是一個函數表達式,字元串代表的值的類型和函數傳回值類型相同,如果函數不存在,傳回None
if func_name_list[0] in func_map:
return call_plugin_func(func_map.get(func_name_list[0]))
else:
for func_name in func_name_list:
if func_name in func_map:
string = string.replace('@%s@' % func_name, str(call_plugin_func(func_map.get(func_name))))
return string
def call_plugin_func(function_express):
'''
調用插件函數
'''
try:
result = REG_FOR_FUNC_NAME_AND_ARGS.findall(function_express) # 查找函數表達式中的函數定義(函數名稱及其參數)
if result:
plugin_func_name, plugin_func_args = result[0]
plugin_func_name = plugin_func_name.strip('_') # 去掉函數字首辨別 _ 以擷取真正的函數
plugin_func_args = plugin_func_args.strip()
plugin_func_arg_list = []
if plugin_func_args:
plugin_func_arg_list = plugin_func_args.split("||") # 函數參數要求用 || 分隔
position_arg_list = [] # 存放位置參數
keyword_arg_dict = {} # 存放關鍵詞參數
for item in plugin_func_arg_list:
item = item.strip()
if REG_FOR_KWARG.findall(item): # 關鍵詞參數
key, value = re.split('\s*=[=|\s]*', item)
try:
value = _replace_function(value)
keyword_arg_dict[key.strip()] = eval(value)
except Exception as e:
keyword_arg_dict[key.strip()] = value
else:
try:
value = _replace_function(item)
position_arg_list.append(eval(value))
except Exception as e:
position_arg_list.append(value)
if plugin_func_name in PUGIN_FUNC_MAP:
return PUGIN_FUNC_MAP.get(plugin_func_name)(*position_arg_list, **keyword_arg_dict)
else:
return None
else: #未找到函數
print('沒有找到同函數表達式( %s )比對的函數定義' % function_express)
return None #
except Exception as e:
raise
def replace_function(string):
'''替換函數'''
try:
regular_obj = re.compile('\${\s*(_.+?)\(', re.DOTALL)
# 擷取臨時函數名稱
temp_func_name_list = REG_FOR_FUNC_NAME_OF_EXP.findall(string)
string_copy = string
old_func_name_set = set() # 存放上一次的查找結果
while old_func_name_set != set(temp_func_name_list):
old_func_name_set = set(temp_func_name_list)
for func_name in temp_func_name_list: # 周遊查找函數對應的函數表達式
pattern = '\${\s*%s\(.*?\)\s*}' % func_name
func_express_list = re.findall(pattern, string_copy) # 擷取函數表達式(因為可能存在函數嵌套,是以擷取的表達式可能是錯誤的)
if not func_express_list: # 找不到函數表達式,說明該函數名稱無效,不合法
continue
for func_express in func_express_list:
temp_func_express = func_express.strip().lstrip('${')
if not REG_FOR_STRICT_FUNC_EXP.findall(temp_func_express): # 表達式不包含嵌套函數,則 擷取正确的函數表達式進行替換
right_func_express_list = REG_FOR_STRICT_FUNC_EXP.findall(func_express)
for right_func_express in right_func_express_list:
string_copy = string_copy.replace(right_func_express, '@plugin_func%s@' % func_name)
func_map['plugin_func%s' % func_name] = right_func_express # 建立臨時函數名稱和函數表達式的映射關系
temp_func_name_list = re.findall(regular_obj, string_copy)
if string_copy == string: # 無變化
return string
return _replace_function(string_copy)
except Exception as e:
print('替換函數出錯%s' % e)
return string
# 運作測試
src_string = "some string ${ __base64( ${__read_file('filepath')} \
|| 'string_arg' || 'b==整個表達式(包括b==)是字元串參數' || '支援單雙引号轉義字元參數\" \
|| 'fake_key_arg1 = 我整個表達式都是字元串參數' || key_arg1='關鍵詞字元串參數'||key_arg2=1 ||key_arg3=[1, 2, 3] \
|| key_arg4={'a':1, 'b':'字典參數'} \
) } hello"
print(replace_function(src_string))
src_string = '${ __generate_num() }'
print(replace_function(src_string))
運作結果如下

作者:授客
QQ:1033553122
全國軟體測試QQ交流群:7156436
Git位址:https://gitee.com/ishouke
友情提示:限于時間倉促,文中可能存在錯誤,歡迎指正、評論!
作者五行缺錢,如果覺得文章對您有幫助,請掃描下邊的二維碼打賞作者,金額随意,您的支援将是我繼續創作的源動力,打賞後如有任何疑問,請聯系我!!!
微信打賞
支付寶打賞 全國軟體測試交流QQ群