diff --git a/JsonPreprocessor/CJsonPreprocessor.py b/JsonPreprocessor/CJsonPreprocessor.py index a2039d8c..87e4d996 100644 --- a/JsonPreprocessor/CJsonPreprocessor.py +++ b/JsonPreprocessor/CJsonPreprocessor.py @@ -49,7 +49,6 @@ import json import re import sys -import platform import copy import shlex @@ -64,21 +63,22 @@ class CSyntaxType(): class CNameMangling(Enum): AVOIDDATATYPE = "JPavoidDataType_" COLONS = "__handleColonsInLine__" - DUPLICATEDKEY_00 = "__rootDuplicatedKey__" + DUPLICATEDKEY_00 = "__handleDuplicatedKey__00" DUPLICATEDKEY_01 = "__handleDuplicatedKey__" - DUPLICATEDKEY_02 = "__RecursiveInitialValue__" STRINGCONVERT = "__ConvertParameterToString__" LISTINDEX = "__IndexOfList__" class CPythonJSONDecoder(json.JSONDecoder): """ - Add python data types and syntax to json. ``True``, ``False`` and ``None`` will be a accepted as json syntax elements. +Extends the JSON syntax by the Python keywords ``True``, ``False`` and ``None``. -**Args:** +**Arguments:** - **json.JSONDecoder** (*object*) +* ``json.JSONDecoder`` - Decoder object provided by ``json.loads`` + / *Type*: object / + + Decoder object provided by ``json.loads`` """ NUMBER_RE = re.compile( @@ -140,65 +140,48 @@ def custom_scan_once(self, string : str, idx : int) -> any: class CJsonPreprocessor(): """ - CJsonPreprocessor extends the syntax of json. - - Features are - - - Allow c/c++-style comments within json files. - - // single line or part of single line and /\* \*/ multline comments are possible - - - Allow to import json files into json files - - ``"[import]" : "relative/absolute path"``, imports another json file to exactly this location. - - - Allow use of the defined paramaters within json files - - In any place the syntax ``${basenode.subnode. ... nodename}`` allows to reference an already existing parameter. - - * Example: - - .. code:: - - { - "basenode" : { - subnode : { - "myparam" : 5 - }, - - }, +CJsonPreprocessor extends the JSON syntax by the following features: - "myVar" : ${basenode.subnode.myparam} - } - - - Allow Python data types ``True``, ``False`` and ``None`` +* Allow c/c++-style comments within JSON files +* Allow to import JSON files into JSON files +* Allow to define and use parameters within JSON files +* Allow Python keywords ``True``, ``False`` and ``None`` """ def getVersion(self): + """ +Returns the version of JsonPreprocessor as string. + """ return VERSION def getVersionDate(self): + """ +Returns the version date of JsonPreprocessor as string. + """ return VERSION_DATE def __init__(self, syntax: CSyntaxType = CSyntaxType.python , currentCfg : dict = {}) -> None: """ - Constructor +Constructor + +**Arguments:** -**Args:** +* ``syntax`` (*CSyntaxType*) optional - **syntax** (*CSyntaxType*) optional + / *Condition*: optional / *Type*: CSyntaxType / *Default*: python / - default: `json` , `python` + If set to ``python``, then Python data types are allowed as part of JSON file. - If set to `python`, then python data types are allowed as part of json file. +* ``currentCfg`` (*dict*) optional - **currentCfg** (*dict*) optional + / *Condition*: optional / *Type*: dict / *Default*: {} / - Internally used to aggregate imported json files. + Internally used to aggregate imported json files. """ import builtins import keyword self.lDataTypes = [name for name, value in vars(builtins).items() if isinstance(value, type)] + self.specialCharacters = r'!@#$%^&*()+=[\]{}|;:\'",<>?/`~' self.lDataTypes.append(keyword.kwlist) self.jsonPath = '' self.lImportedFiles = [] @@ -212,9 +195,9 @@ def __init__(self, syntax: CSyntaxType = CSyntaxType.python , currentCfg : dict self.jsonCheck = {} def __reset(self, bCleanGlobalVars : bool = False) -> None: - ''' - Reset initial variables which are set in constructor method after master Json file is loaded. - ''' + """ +Reset initial variables which are set in constructor method after master JSON file is loaded. + """ self.jsonPath = '' self.lImportedFiles = [] self.recursive_level = 0 @@ -240,25 +223,29 @@ def __reset(self, bCleanGlobalVars : bool = False) -> None: pass def __processImportFiles(self, input_data : dict) -> dict: - ''' - This is a custom decorder of ``json.loads object_pairs_hook`` function. + """ +This is a custom decoder of ``json.loads object_pairs_hook`` function. - This method helps to import json files which are provided in ``"[import]"`` keyword into the current json file. +This method helps to import JSON files which are provided in ``"[import]"`` keyword into the current json file. -**Args:** +**Arguments:** - **input_data** (*dict*) +* ``input_data`` - Dictionary from json file as input + / *Condition*: required / *Type*: (* / + + Dictionary from JSON file as input **Returns:** - **out_dict** (*dict*) +* ``out_dict`` - Dictionary as output, dictionary is extended if ``"[import]"`` found and properly imported. - ''' - out_dict = {} + / *Type*: dict / + Dictionary with resolved content from imported JSON file + """ + out_dict = {} + i=1 for key, value in input_data: if re.match('^\s*\[\s*import\s*\]\s*', key.lower()): currJsonPath = self.jsonPath @@ -288,18 +275,18 @@ def __processImportFiles(self, input_data : dict) -> dict: if self.bDuplicatedKeys: tmpOutdict = copy.deepcopy(out_dict) for k1, v1 in tmpOutdict.items(): - pattern2 = "\${\s*[0-9A-Za-z\.\-_]*\.*" + k1 + "\s*}$|\[\s*'" + k1 + "'\s*\]$" - if re.search(pattern2, key): + pattern2 = rf'\${{\s*[^{re.escape(self.specialCharacters)}]*\.*' + k1 + r'\s*}$|\[\s*\'' + k1 + r'\'\s*\]$' + if re.search(pattern2, key, re.UNICODE): bCheck = True - tmpKey = key.replace("${", "") - tmpKey = tmpKey.replace("}", "") + dReplacements = {"${" : "", "}" : ""} + tmpKey = self.__multipleReplace(key, dReplacements) items = [] if "." in tmpKey: items = tmpKey.split(".") - elif re.search("\['[0-9A-Za-z\.\-_]+'\]", tmpKey): + elif re.search(rf'\[\'[^{re.escape(self.specialCharacters)}]+\'\]', tmpKey, re.UNICODE): try: - rootKey = re.search("^\s*([0-9A-Za-z\.\-_]+)\['.+", tmpKey)[1] - items = re.findall("\['([0-9A-Za-z\.\-_]+)'\]", tmpKey) + rootKey = re.search(rf'^\s*([^{re.escape(self.specialCharacters)}]+)\[\'.+', tmpKey, re.UNICODE)[1] + items = re.findall(rf'\[\'([^{re.escape(self.specialCharacters)}]+)\'\]', tmpKey, re.UNICODE) items.insert(0, rootKey) except: pass @@ -314,6 +301,14 @@ def __processImportFiles(self, input_data : dict) -> dict: if bCheck: key = k1 if k1 == key: + listKeys = list(out_dict.keys()) + index = listKeys.index(key) + newKey = key + CNameMangling.DUPLICATEDKEY_01.value + str(i) + listKeys.insert(index, newKey) + tmpDict = {} + for k in listKeys: + tmpDict[k] = index if k==newKey else out_dict[k] + out_dict = tmpDict if isinstance(out_dict[key], list): if out_dict[key][0] != CNameMangling.DUPLICATEDKEY_01.value: tmpValue = [CNameMangling.DUPLICATEDKEY_01.value, out_dict[key], value] @@ -329,28 +324,29 @@ def __processImportFiles(self, input_data : dict) -> dict: out_dict[key] = value del tmpOutdict out_dict[key] = value + i+=1 return out_dict def __load_and_removeComments(self, jsonFile : str) -> str: """ - Loads a given json file and filters all C/C++ style comments. +Loads a given json file and filters all C/C++ style comments. -**Args:** +**Arguments:** - **jsonFile** (*string*) +* ``jsonFile`` - Path (absolute/relative/) file to be processed. - The path can contain windows/linux style environment variables. + / *Condition*: required / *Type*: str / - !ATTENTION! This is dangerous + Path of file to be processed. **Returns:** - **sContentCleaned** (*string*) +* ``sContentCleaned`` - String version of json file after removing all comments. - """ + / *Type*: str / + String version of JSON file after removing all comments. + """ def replacer(match): s = match.group(0) if s.startswith('/'): @@ -368,176 +364,189 @@ def replacer(match): def __checkParamName(self, sInput: str) -> str: """ - Checks a parameter name, in case the name is conflict with Python keywords, the temporary prefix - will be added to avoid any potential issues. This temporary prefix is removed when updating returned - Json object. +Checks a parameter name, in case the name is conflict with Python keywords, the temporary prefix +will be added to avoid any potential issues. This temporary prefix is removed when updating returned +Json object. + +**Arguments:** -**Args:** +* ``sInput`` - **sInput** (*string*) + / *Condition*: required / *Type*: str / **Returns:** - **sInput** (*string*) +* ``sInput`` + + / *Type*: str / """ - pattern = "\${\s*([0-9A-Za-z_]+[0-9A-Za-z\.\-_]*)\s*}" - lParams = re.findall(pattern, sInput) + pattern = rf'\${{\s*([^{re.escape(self.specialCharacters)}]+)\s*}}' + lParams = re.findall(pattern, sInput, re.UNICODE) for param in lParams: if "." not in param and param in self.lDataTypes: sInput = re.sub(param, CNameMangling.AVOIDDATATYPE.value + param, sInput, count=1) if "." in param and CNameMangling.AVOIDDATATYPE.value + param.split('.')[0] in globals(): sInput = re.sub(param, CNameMangling.AVOIDDATATYPE.value + param, sInput, count=1) return sInput + + def __multipleReplace(self, sInput : str, dReplacements : str) -> str: + """ + Replaces multiple parts in a string. + +**Arguments:** - def __nestedParamHandler(self, sInputStr : str, bKey = False) -> list: - ''' - This method handles nested variables in parameter names or values. Variable syntax is ${Variable_Name}. +* ``sInput`` -**Args:** + / *Condition*: required / *Type*: str / - **sInputStr** (*string*) +**Returns:** + +* ``sOutput`` - Parameter name or value which contains a nested variable. + / *Type*: str / + + """ + pattern = re.compile('|'.join(re.escape(key) for key in dReplacements.keys())) + sOutput = pattern.sub(lambda x: dReplacements[x.group()], sInput) + return sOutput + + def __nestedParamHandler(self, sInputStr : str, bKey = False, bConvertToStr = False): + """ +This method handles nested variables in parameter names or values. Variable syntax is ${Variable_Name}. + +**Arguments:** + +* ``sInputStr`` + + / *Condition*: required / *Type*: str / + + Parameter name or value which contains a nested variable. **Returns:** - **lNestedParam** (*list*) - - List of resolved variables which contains in the sInputStr. - ''' - def __referVarHandle(referVar : str, sInputStr : str) -> str: - if "." in referVar: - dotdictVariable = re.sub('\$\${\s*(.*?)\s*}', '\\1', referVar) - lDotdictVariable = dotdictVariable.split(".") - lParams = self.__handleDotdictFormat(lDotdictVariable, []) - rootElement = lParams[0] - sParam = '$${' + rootElement + '}' - lParams.pop(0) - for item in lParams: - sParam = sParam + "[" + item + "]" if re.match("^\d+$", item) else sParam + "['" + item + "']" - sInputStr = re.sub(referVar.replace("$", "\$"), sParam, sInputStr) - referVar = '$${' + rootElement + '}' - tmpReferVar = re.sub("\$", "\\$", referVar) - pattern = '(' + tmpReferVar + '\s*(\[+\s*.*?\s*\]+)*)' - variable = re.search(pattern, sInputStr) - return variable[0] - - pattern = "\$\${\s*[0-9A-Za-z_]+[0-9A-Za-z\.\-_]*\s*}" - referVars = re.findall("(" + pattern + ")", sInputStr) - while sInputStr.count("$$") > len(referVars): +* ``lNestedParam`` + + / *Type*: list / + + List of resolved variables which contains in the ``sInputStr``. + """ + def __getNestedValue(sNestedParam : str): + dReplacements = {"$${" : "", "}" : ""} + sParameter = self.__multipleReplace(sNestedParam, dReplacements) + sExec = "value = " + sParameter + try: + ldict = {} + exec(sExec, globals(), ldict) + tmpValue = ldict['value'] + except: + self.__reset(bCleanGlobalVars=True) + raise Exception(f"The variable '{sNestedParam.replace('$$', '$')}' is not available!") + if bKey and (isinstance(tmpValue, list) or isinstance(tmpValue, dict)): + self.__reset(bCleanGlobalVars=True) + errorMsg = f"Found expression '{sNestedParam.replace('$$', '$')}' with at least one parameter of composite data type \ +('{sNestedParam.replace('$$', '$')}' is of type {type(tmpValue)}). Because of this expression is the name of a parameter, \ +only simple data types are allowed to be substituted inside." + raise Exception(errorMsg) + return tmpValue + + def __handleDotInNestedParam(sNestedParam : str) -> str: + ddVar = re.sub(r'\$\${\s*(.*?)\s*}', '\\1', sNestedParam, re.UNICODE) + lddVar = ddVar.split(".") + lElements = self.__handleDotdictFormat(lddVar, []) + sVar = '$${' + lElements[0] + '}' + lElements.pop(0) + for item in lElements: + sVar = sVar + "[" + item + "]" if re.match(r'^\d+$', item) else sVar + "['" + item + "']" + return sVar + + pattern = rf'\$\${{\s*[^{re.escape(self.specialCharacters)}]+\s*}}' + referVars = re.findall("(" + pattern + ")", sInputStr, re.UNICODE) + # Resolve dotdict in sInputStr + for var in referVars: + if var not in sInputStr: + continue + if "." in var: + sVar = __handleDotInNestedParam(var) + sInputStr = sInputStr.replace(var, sVar) + tmpPattern = pattern + rf'(\[\s*\d+\s*\]|\[\s*\'[^{re.escape(self.specialCharacters)}]+\'\s*\])*' + sNestedParam = sInputStr.replace("$$", "$") + while re.search(tmpPattern, sInputStr, re.UNICODE) and sInputStr.count("$$")>1: + sLoopCheck = sInputStr + referVars = re.findall(r'(' + tmpPattern + r')[^\[]', sInputStr, re.UNICODE) + if len(referVars)==0: + referVars = re.findall(r'(' + tmpPattern + r')$', sInputStr, re.UNICODE) for var in referVars: - if var not in sInputStr: - continue - if "." in var: - ddVar = re.sub('\$\${\s*(.*?)\s*}', '\\1', var) - lddVar = ddVar.split(".") - lElements = self.__handleDotdictFormat(lddVar, []) - sVar = '$${' + lElements[0] + '}' - lElements.pop(0) - for item in lElements: - sVar = sVar + "[" + item + "]" if re.match("^\d+$", item) else sVar + "['" + item + "']" - sInputStr = sInputStr.replace(var, sVar) - else: - sVar = var - rootVar = re.search('^\s*\$\${(\s*.*?)}', sVar).group(1) - tmpVar = re.sub("\$", "\\$", sVar) - tmpVar = re.sub("((\[\s*'[^\$\[\]\(\)]+'\s*\]|\[\s*\d+\s*\])*)", "", tmpVar) - subPattern = tmpVar + "((\[\s*'[^\$\[\]\(\)]+'\s*\]|\[\s*\d+\s*\])*)" - subVar = re.search(subPattern, sInputStr).group(1) - sExec = "value = " + rootVar + subVar - try: - ldict = {} - exec(sExec, globals(), ldict) - tmpValue = ldict['value'] - except: - self.__reset(bCleanGlobalVars=True) - raise Exception(f"The variable '{var.replace('$$', '$')}' is not available!") - if bKey and (isinstance(tmpValue, list) or isinstance(tmpValue, dict)): - self.__reset(bCleanGlobalVars=True) - raise Exception(f"Overwrite the element '{sInputStr.replace('$$', '$')}' failed! \ -Due to the datatype of '{sVar.replace('$$', '$')}' is '{type(tmpValue)}'. Only simple data types are allowed to be substituted inside.") - subPattern = "(" + tmpVar + "(\[\s*'[^\$\[\]\(\)]+'\s*\]|\[\s*\d+\s*\])*)" - var = re.sub("\$", "\\$", re.search(subPattern, sInputStr).group(1)) - if re.search("\[.+\]", var): - var = var.replace("[", "\[") - var = var.replace("]", "\]") - sInputStr = re.sub(var, tmpValue, sInputStr) if isinstance(tmpValue, str) else \ - re.sub(var, str(tmpValue), sInputStr) - referVars = re.findall("(" + pattern + ")", sInputStr) - lNestedParam = [] - if len(referVars) > 1: - if not bKey: - for referVar in referVars: - lNestedParam.append(re.sub("\$\$", "$", __referVarHandle(referVar, sInputStr))) - return lNestedParam - else: - sUpdateVar = referVars[0] - referVars = referVars[1:] - tmpPattern = re.sub("\$", "\\$", sUpdateVar) - sInputStr = re.sub(tmpPattern, '', sInputStr, count=1) - for var in referVars[::-1]: - pattern = '(' + var.replace('$', '\$') + '\s*\[\s*.*?\s*\])' - variable = re.findall(pattern, sInputStr) - if variable == []: - sExec = "value = " + re.search('^\s*\$\${(\s*.*?)}', var).group(1) - try: - ldict = {} - exec(sExec, globals(), ldict) - tmpValue = ldict['value'] - except: - self.__reset(bCleanGlobalVars=True) - raise Exception(f"The variable '{var}' is not available!") - if re.search("\[\s*"+ var.replace('$', '\$') +"\s*\]", sInputStr) and isinstance(tmpValue, str): - sInputStr = sInputStr.replace(var, "'" + var + "'") - sInputStr = re.sub(var.replace('$', '\$'), tmpValue, sInputStr) if isinstance(tmpValue, str) else \ - re.sub(var.replace('$', '\$'), str(tmpValue), sInputStr) - continue - while variable != []: - fullVariable = variable[0] - pattern = pattern[:-1] + '\[\s*.*?\s*\])' - variable = re.findall(pattern, sInputStr) - if variable != []: - fullVariable = variable[0] - sExec = "value = " + re.sub('\$\${\s*(.*?)\s*}', '\\1', fullVariable) - try: - ldict = {} - exec(sExec, globals(), ldict) - tmpValue = ldict['value'] - except: + sVar = __handleDotInNestedParam(var[0]) if "." in var[0] else var[0] + tmpValue = __getNestedValue(sVar) + while var[0] in sInputStr: + sLoopCheck1 = sInputStr + dReplacements = {"$" : "\$", "[" : "\[", "]" : "\]"} + varPattern = self.__multipleReplace(var[0], dReplacements) + if re.search(r"\[['\s]*" + varPattern + r"['\s]*\]", sInputStr): + if re.search(r"\[\s*'\s*" + varPattern + r"\s*'\s*\]", sInputStr): + sInputStr = re.sub(r"\[\s*'\s*" + varPattern + r"\s*'\s*\]", "['" + str(tmpValue) + "']", sInputStr) + elif isinstance(tmpValue, str): + sInputStr = re.sub(r"\[['\s]*" + varPattern + r"['\s]*\]", "['" + tmpValue + "']", sInputStr) + elif isinstance(tmpValue, int): + sInputStr = re.sub(r"\[['\s]*" + varPattern + r"['\s]*\]", "[" + str(tmpValue) + "]", sInputStr) + else: + sInputStr = sInputStr.replace("$$", "$") + var = var[0].replace("$$", "$") + errorMsg = f"Invalid index or dictionary key in parameter '{sInputStr}'. The datatype of variable \ +'{var}' have to 'int' or 'str'." + raise Exception(errorMsg) + else: + sInputStr = sInputStr.replace(var[0], str(tmpValue)) + if sInputStr==sLoopCheck1: self.__reset(bCleanGlobalVars=True) - raise Exception(f"The variable '{fullVariable}' is not available!") - pattern = re.sub('\[', '\\[', fullVariable) - pattern = re.sub('\]', '\\]', pattern) - pattern = re.sub("\$", "\\$", pattern) - sInputStr = re.sub(pattern, '\'' + tmpValue + '\'', sInputStr) if isinstance(tmpValue, str) else \ - re.sub(pattern, '\'' + str(tmpValue) + '\'', sInputStr) - sKeyHandled = sUpdateVar + sInputStr - lNestedParam.append(re.sub("\$\$", "$", __referVarHandle(sUpdateVar, sKeyHandled))) - return lNestedParam - else: - lNestedParam.append(re.sub("\$\$", "$", __referVarHandle(referVars[0], sInputStr))) - return lNestedParam + raise Exception(f"Infinity loop detection while handling the parameter '{sNestedParam}'.") + if sInputStr==sLoopCheck: + self.__reset(bCleanGlobalVars=True) + raise Exception(f"Infinity loop detection while handling the parameter '{sNestedParam}'.") + if sInputStr.count("$${")==1: + tmpPattern = pattern + rf'(\[\s*\d+\s*\]|\[\s*\'[^{re.escape(self.specialCharacters)}]+\'\s*\])*' + if re.match("^" + tmpPattern + "$", sInputStr.strip(), re.UNICODE) and bKey and not bConvertToStr: + rootVar = re.search(pattern, sInputStr, re.UNICODE)[0] + sRootVar = __handleDotInNestedParam(rootVar) if "." in rootVar else rootVar + sInputStr = sInputStr.replace(rootVar, sRootVar) + dReplacements = {"$${" : "", "}" : ""} + return self.__multipleReplace(sInputStr, dReplacements) + var = re.search(tmpPattern, sInputStr, re.UNICODE) + rootVar = re.search(pattern, var[0], re.UNICODE)[0] + sRootVar = __handleDotInNestedParam(rootVar) if "." in rootVar else rootVar + sVar = var[0].replace(rootVar, sRootVar) + tmpValue = __getNestedValue(sVar) + if re.match(r"^\s*" + tmpPattern + r"\s*$", sInputStr, re.UNICODE) and not bKey: + return tmpValue + else: + sInputStr = sInputStr.replace(var[0], str(tmpValue)) + return sInputStr.replace("$$", "$") if "$$" in sInputStr else sInputStr def __handleDotdictFormat(self, lInputListParams : list, lParams: list = []) -> list: - ''' - This method checks the availability of param names contained "." in dotdict format element in JSON config file. + """ +This method checks the availability of param names contained "." in dotdict format element in JSON config file. + +**Arguments:** + +* ``lInputListParams`` -**Args:** + / *Condition*: required / *Type*: list / - **lInputListParams** (*list*) + List of items separated by "." of dotdict format. - List of items which separated by "." of dotdict format element. +* ``lParams`` - **lParams** (*list*) + / *Type*: list / - List of param names in dotdict format element. + List of parameter names in dotdict format. **Returns:** - **lParams** (*list*) +* ``lParams`` - ''' + / *Type*: list / + """ checkParam = lInputListParams[0] i = 0 bDotdictParam = False @@ -559,11 +568,11 @@ def __handleDotdictFormat(self, lInputListParams : list, lParams: list = []) -> return self.__handleDotdictFormat(lInputListParams, lParams) def __checkAndCreateNewElement(self, sKey: str, value, bCheck=False, keyNested=''): - ''' - This method check and create new elements if they are not exist. - ''' - rootKey = re.sub("\[.*\]", "", sKey) - subElements = re.findall("\[\s*'([0-9A-Za-z_]+[0-9A-Za-z\.\-_]*)'\s*\]", sKey) + """ +This method checks and creates new elements if they are not already existing. + """ + rootKey = re.sub(r'\[.*\]', "", sKey, re.UNICODE) + subElements = re.findall(rf"\[\s*'([^{re.escape(self.specialCharacters)}]*)'\s*\]", sKey, re.UNICODE) if len(subElements) < 1: return True else: @@ -597,29 +606,34 @@ def __checkAndCreateNewElement(self, sKey: str, value, bCheck=False, keyNested=' continue return True - def __updateAndReplaceNestedParam(self, oJson : dict, bNested : bool = False, recursive : bool = False): - ''' - This method replaces all nested parameters in key and value of a json object . + def __updateAndReplaceNestedParam(self, oJson : dict, bNested : bool = False, recursive : bool = False, parentParams : str = ''): + """ +This method replaces all nested parameters in key and value of a JSON object . + +**Arguments:** -**Args:** +* ``oJson`` - **oJson** (*dict*) + / *Condition*: required / *Type*: dict / - Input Json object as dictionary. This dictionary will be searched for all ``${variable}`` occurences. - If found it will be replaced with it's current value. + Input JSON object as dictionary. This dictionary will be searched for all ``${variable}`` occurences. + If found it will be replaced with it's current value. **Returns:** - **oJsonOut** (*dict*) +* ``oJsonOut`` + + / *Type*: dict / - Output Json object as dictionary with all variables resolved. - ''' + Output JSON object as dictionary with all variables resolved. + """ - def __jsonUpdated(k, v, oJson, bNested, keyNested = ''): + def __jsonUpdated(k, v, oJson, bNested, keyNested = '', bDuplicatedHandle=False, recursive = False): if keyNested != '': - del oJson[keyNested] - rootKey = re.sub("\[.*\]", "", k) - if re.search("^[0-9]+.*$", rootKey): + if not bDuplicatedHandle and keyNested in oJson.keys(): + del oJson[keyNested] + rootKey = re.sub(r'\[.*\]', "", k, re.UNICODE) + if re.search(r'^[0-9]+.*$', rootKey, re.UNICODE): oJson[f"{rootKey}"] = {} elif rootKey not in globals(): oJson[rootKey] = {} @@ -628,7 +642,7 @@ def __jsonUpdated(k, v, oJson, bNested, keyNested = ''): exec(sExec, globals()) except Exception as error: raise Exception(f"Could not set root key element '{rootKey}'! Reason: {error}") - if re.match("^[0-9A-Za-z_]+[0-9A-Za-z\.\-_]*\[.+\]+$", k): + if re.match(rf"^[^{re.escape(self.specialCharacters)}]+\[.+\]+$", k, re.UNICODE): self.__checkAndCreateNewElement(k, v, keyNested=keyNested) sExec = k + " = \"" + v + "\"" if isinstance(v, str) else k + " = " + str(v) try: @@ -640,8 +654,23 @@ def __jsonUpdated(k, v, oJson, bNested, keyNested = ''): if CNameMangling.AVOIDDATATYPE.value in k: k = re.sub(CNameMangling.AVOIDDATATYPE.value, "", k) + if not recursive: + checkVar = "oJson['" + k.split('[', 1)[0] + "'][" + k.split('[', 1)[1] + subElements = re.findall(rf"\[\s*'([^{re.escape(self.specialCharacters)}]*)'\s*\]", checkVar, re.UNICODE) + checkVar = "oJson" + for element in subElements: + checkVar = checkVar + "['" + element + "']" + sExec = "dummyData = " + checkVar + try: + exec(sExec) + except: + sExec = checkVar + " = {}" + try: + exec(sExec) + except: + pass if isinstance(v, str): - sExec = "oJson['" + k.split('[', 1)[0] + "'][" + k.split('[', 1)[1] + " = \"" + v + "\"" + sExec = "oJson['" + k.split('[', 1)[0] + "'][" + k.split('[', 1)[1] + " = '" + v + "'" else: sExec = "oJson['" + k.split('[', 1)[0] + "'][" + k.split('[', 1)[1] + " = " + str(v) try: @@ -658,86 +687,32 @@ def __jsonUpdated(k, v, oJson, bNested, keyNested = ''): if bNested: if CNameMangling.AVOIDDATATYPE.value in k: k = re.sub(CNameMangling.AVOIDDATATYPE.value, "", k) - oJson[k] = v - globals().update({k:v}) + oJson[k] = v + globals().update({k:v}) def __loadNestedValue(initValue: str, sInputStr: str, bKey=False, key=''): - varPattern = "\${\s*[0-9A-Za-z_]+[0-9A-Za-z\.\-_]*\s*}" - indexPattern = "\[\s*-*\d*\s*:\s*-*\d*\s*\]" - dictPattern = "(\[+\s*'[^\$\[\]\(\)]+'\s*\]+|\[+\s*\d+\s*\]+|\[+\s*" + varPattern + ".*\]+)*|" + indexPattern + varPattern = rf"\${{\s*[^{re.escape(self.specialCharacters)}]*\s*}}" + indexPattern = r"\[\s*-*\d*\s*:\s*-*\d*\s*\]" + dictPattern = r"(\[+\s*'[^\$\[\]\(\)]+'\s*\]+|\[+\s*\d+\s*\]+|\[+\s*" + varPattern + r".*\]+)*|" + indexPattern pattern = varPattern + dictPattern - bStringValue = False bValueConvertString = False if CNameMangling.STRINGCONVERT.value in sInputStr: bValueConvertString = True sInputStr = sInputStr.replace(CNameMangling.STRINGCONVERT.value, '') + sInputStr = re.sub("\$", "$$", sInputStr) initValue = initValue.replace(CNameMangling.STRINGCONVERT.value, '') - if re.search("(str\(\s*" + pattern + "\))", sInputStr.lower()): - sInputStr = re.sub("str\(\s*(" + pattern + ")\s*\)", "$\\1", sInputStr) - bStringValue = True - elif re.match("^\s*" + pattern + "\s*$", sInputStr): + elif re.match(r"^\s*" + pattern + r"\s*$", sInputStr, re.UNICODE): sInputStr = re.sub("\$", "$$", sInputStr) - else: + if sInputStr.count("${") > sInputStr.count("}"): self.__reset(bCleanGlobalVars=True) - while "str(" in initValue: - initValue = re.sub("str\(([\${}0-9A-Za-z\.\-_\[\]]+)\)", "\\1", initValue, count=1) raise Exception(f"Invalid syntax! One or more than one opened or closed curly bracket is missing in expression '{initValue}'.\n \ Please check the configuration file of the executed test!") sInputStr = self.__checkParamName(sInputStr) - valueAfterProcessed = self.__nestedParamHandler(sInputStr) if not bValueConvertString else \ - self.__nestedParamHandler(sInputStr, bKey=bKey) - for valueProcessed in valueAfterProcessed: - if re.search("'\${\s*(.*?)\s*}'", valueProcessed): - tmpNestedList = re.findall("'(\${\s*.*?\s*})'", valueProcessed) - for elem in tmpNestedList: - tmpVar = elem.replace('${', '') - tmpVar = tmpVar.replace('}', '') - tmpValue = None - try: - ldict = {} - exec(f"value = {tmpVar}", globals(), ldict) - tmpValue = ldict['value'] - del ldict - except: - pass - if tmpValue is not None: - tmpNestedParam = valueProcessed - valueProcessed = valueProcessed.replace(elem, str(tmpValue)) - if tmpNestedParam in self.lNestedParams: - self.lNestedParams.remove(tmpNestedParam) - self.lNestedParams.append(valueProcessed) - tmpValueAfterProcessed = re.sub("'*\${\s*(.*?)\s*}'*", '\\1', valueProcessed) - sExec = "value = " + tmpValueAfterProcessed if isinstance(tmpValueAfterProcessed, str) else \ - "value = " + str(tmpValueAfterProcessed) - try: - ldict = {} - exec(sExec, globals(), ldict) - if bStringValue: - pattern = "\${\s*[0-9A-Za-z_]+[0-9A-Za-z\.\-_]*\s*}(\[+\s*'[^\$]+'\s*\]+|\[+\s*\d+\s*\]+)*" - sInputStr = re.sub("(\$" + pattern + ")", str(ldict['value']), sInputStr, count=1) - else: - sInputStr = re.sub("\$\$", "$", sInputStr) - sInputStr = str(ldict['value']) if bValueConvertString else ldict['value'] - except: - self.__reset(bCleanGlobalVars=True) - if CNameMangling.DUPLICATEDKEY_00.value in valueProcessed: - valueProcessed = valueProcessed.replace(CNameMangling.DUPLICATEDKEY_00.value, '') - elif CNameMangling.DUPLICATEDKEY_02.value in valueProcessed: - valueProcessed = re.sub("(" + CNameMangling.DUPLICATEDKEY_02.value + "[0-9]*)", "", valueProcessed) - raise Exception(f"The variable '{valueProcessed}' is not available!") - if bKey and type(ldict['value']) in [list, dict]: - if CNameMangling.AVOIDDATATYPE.value in valueProcessed: - valueProcessed = valueProcessed.replace(CNameMangling.AVOIDDATATYPE.value, '') - self.__reset(bCleanGlobalVars=True) - while 'str(' in key: - key = re.sub("str\(([0-9A-Za-z\._\${}'\[\]]+)\)", "\\1", key) - errorMsg = f"Found expression '{key}' with at least one parameter of composite data type \ -('{valueProcessed}' is of type {type(ldict['value'])}). Because of this expression is the name of a parameter, \ -only simple data types are allowed to be substituted inside." - raise Exception(errorMsg) - if "${" not in str(sInputStr): - break - return sInputStr + handledValue = self.__nestedParamHandler(sInputStr) if not bValueConvertString else \ + self.__nestedParamHandler(sInputStr, bKey=bKey, bConvertToStr=bValueConvertString) + if bValueConvertString and not isinstance(handledValue, str): + handledValue = str(handledValue) + return handledValue if bool(self.currentCfg) and not recursive: for k, v in self.currentCfg.items(): @@ -746,41 +721,46 @@ def __loadNestedValue(initValue: str, sInputStr: str, bKey=False, key=''): globals().update({k:v}) tmpJson = copy.deepcopy(oJson) - pattern = "\${\s*[0-9A-Za-z_]+[0-9A-Za-z\.\$\{\}\-_]*\s*}(\[+\s*'.+'\s*\]+|\[+\s*\d+\s*\]+|\[+\s*\${.+\s*\]+)*" + sepecialCharacters = r'!@#%^&*()+=[\]|;:\'",<>?/`~' + pattern = rf"\${{\s*[^{re.escape(sepecialCharacters)}]+\s*}}" + pattern = pattern + r"(\[+\s*'.+'\s*\]+|\[+\s*\d+\s*\]+|\[+\s*\${.+\s*\]+)*" for k, v in tmpJson.items(): + if "${" not in k and CNameMangling.DUPLICATEDKEY_01.value not in k: + parentParams = k if parentParams=='' else parentParams + "['" + k + "']" keyNested = '' bStrConvert = False bImplicitCreation = False - if CNameMangling.DUPLICATEDKEY_00.value in k: - del oJson[k] - k = k.replace(CNameMangling.DUPLICATEDKEY_00.value, '') - oJson[k] = v - if re.search("(str\(" + pattern + "\))", k.lower()): - bStrConvert = True - keyNested = k - bNested = True - while "${" in k: - k = __loadNestedValue(keyNested, k, bKey=True, key=keyNested) - elif CNameMangling.STRINGCONVERT.value in k: + bDuplicatedHandle = False + if re.match(r"^.+" + CNameMangling.DUPLICATEDKEY_01.value + r"\d+$", k, re.UNICODE): + bDuplicatedHandle = True if CNameMangling.DUPLICATEDKEY_00.value not in k else False + dupKey = k + if CNameMangling.DUPLICATEDKEY_00.value in k: + origKey = re.sub(CNameMangling.DUPLICATEDKEY_01.value + r"\d+$", "", k) + oJson = self.__changeDictKey(oJson, k, origKey) + k = origKey + else: + del oJson[k] + k = re.sub(CNameMangling.DUPLICATEDKEY_01.value + r"\d+$", "", k) + if CNameMangling.STRINGCONVERT.value in k: bStrConvert = True del oJson[k] keyNested = k.replace(CNameMangling.STRINGCONVERT.value, '') oJson[keyNested] = v bNested = True while "${" in k: + sLoopCheck = k k = __loadNestedValue(keyNested, k, bKey=True, key=keyNested) - elif re.match("^\s*" + pattern + "\s*$", k.lower()): + if k == sLoopCheck: + self.__reset(bCleanGlobalVars=True) + raise Exception(f"Infinity loop detection while handling the parameter '{keyNested}'.") + elif re.match(r"^\s*" + pattern + r"\s*$", k, re.UNICODE): keyNested = k - if re.search("\[\s*'*" + pattern + "'*\s*\]", keyNested) or \ - re.search("\." + pattern + "[\.}]+", keyNested): + if re.search(r"\[\s*'*" + pattern + r"'*\s*\]", keyNested, re.UNICODE) or \ + re.search(r"\." + pattern + r"[\.}]+", keyNested, re.UNICODE): bImplicitCreation = True k = re.sub("\$", "$$", k) k = self.__checkParamName(k) - keyAfterProcessed = self.__nestedParamHandler(k, bKey=True) - k = re.sub("\$\$", "$", k) - k = re.sub('^\s*\${\s*(.*?)\s*}', '\\1', keyAfterProcessed[0]) - # Temporary disable implicit creation of data structures based on nested parameters. - # In case check sub-element returns False -> reset() and raise an exception. + k = self.__nestedParamHandler(k, bKey=True) if bImplicitCreation and not self.__checkAndCreateNewElement(k, v, bCheck=True, keyNested=keyNested): self.__reset(bCleanGlobalVars=True) raise Exception(f"The implicit creation of data structures based on nested parameter is not supported. \ @@ -789,56 +769,76 @@ def __loadNestedValue(initValue: str, sInputStr: str, bKey=False, key=''): self.__reset(bCleanGlobalVars=True) raise Exception(f"Could not overwrite parameter {k} due to wrong format.\n \ Please check key '{k}' in config file!!!") - + if isinstance(v, dict): - v, bNested = self.__updateAndReplaceNestedParam(v, bNested, recursive=True) - + v, bNested = self.__updateAndReplaceNestedParam(v, bNested, recursive=True, parentParams=parentParams) elif isinstance(v, list): - if v[0] != CNameMangling.DUPLICATEDKEY_01.value: - tmpValue = [] - for item in v: - if isinstance(item, str) and re.search(pattern, item.lower()): - bNested = True - initItem = item - while isinstance(item, str) and "${" in item: - item = __loadNestedValue(initItem, item) - - tmpValue.append(item) - v = tmpValue - del tmpValue - else: - i=1 - while i str: - ''' - This function checks and makes up all nested parameters in json configuration files. + """ +This function checks and makes up all nested parameters in JSON configuration files. + +**Arguments:** -**Args:** - **sInputStr** (*string*) - Key or value which is parsed from json configuration file. +* ``sInputStr*`` + + / *Condition*: required / *Type*: str / + + Key or value which is parsed from JSON configuration file. **Returns:** The string after nested parameters are made up. @@ -847,19 +847,20 @@ def __checkAndUpdateKeyValue(self, sInputStr: str, nestedKey = False) -> str: Nested param ${abc}['xyz'] -> "${abc}['xyz']" - Nested param "${abc}['xyz']" -> "str(${abc}['xyz'])" - ''' + Nested param "${abc}['xyz']" -> "${abc}['xyz']__ConvertParameterToString__" + """ def __recursiveNestedHandling(sInputStr: str, lNestedParam: list) -> str: - ''' - This method handles nested parameters are called recursively in a string value. - ''' + """ +This method handles nested parameters are called recursively in a string value. + """ tmpList = [] for item in lNestedParam: item = re.sub(r'([$()\[\]])', r'\\\1', item) - pattern = "(\${\s*[0-9A-Za-z\.\-_]*" + item + "[0-9A-Za-z\.\-_]*\s*}(\[\s*.+\s*\])*)" - if re.search(pattern, sInputStr): - sInputStr = re.sub("(" + pattern + ")", "str(\\1)", sInputStr) - tmpResults = re.findall("(str\(" + pattern + "\))", sInputStr) + pattern = rf"(\${{\s*[^{re.escape(self.specialCharacters)}]*" + item + \ + rf"[^{re.escape(self.specialCharacters)}]*\s*}}(\[\s*.+\s*\])*)" + if re.search(pattern, sInputStr, re.UNICODE): + sInputStr = re.sub("(" + pattern + ")", "\\1" + CNameMangling.STRINGCONVERT.value, sInputStr, re.UNICODE) + tmpResults = re.findall("(" + pattern + CNameMangling.STRINGCONVERT.value + ")", sInputStr, re.UNICODE) for result in tmpResults: tmpList.append(result[0]) if tmpList != []: @@ -867,112 +868,69 @@ def __recursiveNestedHandling(sInputStr: str, lNestedParam: list) -> str: return sInputStr - variablePattern = "[0-9A-Za-z_]+[0-9A-Za-z\.\-_]*" - indexPattern = "\[\s*-*\d*\s*:\s*-*\d*\s*\]" - dictPattern = "\[+\s*'.+'\s*\]+|\[+\s*\d+\s*\]+|\[+\s*\${\s*" + variablePattern + "\s*}.*\]+|" + indexPattern - nestedPattern = "\${\s*" + variablePattern + "(\${\s*" + variablePattern + "\s*})*" + "\s*}(" + dictPattern +")*" - valueStrPattern = "[\"|\']\s*[0-9A-Za-z_\-\s*]+[\"|\']" - valueNumberPattern = "[0-9\.]+" + variablePattern = rf"[^{re.escape(self.specialCharacters)}]+" + indexPattern = r"\[\s*-*\d*\s*:\s*-*\d*\s*\]" + dictPattern = r"\[+\s*'.+'\s*\]+|\[+\s*\d+\s*\]+|\[+\s*\${\s*" + variablePattern + r"\s*}.*\]+|" + indexPattern + nestedPattern = r"\${\s*" + variablePattern + r"(\${\s*" + variablePattern + r"\s*})*" + r"\s*}(" + dictPattern + r")*" + valueStrPattern = r"[\"|\']\s*[0-9A-Za-z_\-\s*]+[\"|\']" + valueNumberPattern = r"[0-9\.]+" if "${" in sInputStr: - if re.search("\[[0-9\s]*[A-Za-z_]+[0-9\s]*\]", sInputStr): + if re.search(r"\[[0-9\s]*[A-Za-z_]+[0-9\s]*\]", sInputStr, re.UNICODE): self.__reset(bCleanGlobalVars=True) raise Exception(f"Invalid syntax! A sub-element in {sInputStr.strip()} has to enclosed in quotes.") - if re.match("^\s*" + nestedPattern + "\s*,*\]*}*\s*$", sInputStr.lower()): - sInputStr = re.sub("(" + nestedPattern + ")", "\"\\1\"", sInputStr) - nestedParam = re.sub("^\s*\"(.+)\"\s*.*$", "\\1", sInputStr) + if re.match(r"^\s*" + nestedPattern + r"[\s,\]}]*$", sInputStr, re.UNICODE): + sInputStr = re.sub("(" + nestedPattern + ")", "\"\\1\"", sInputStr, re.UNICODE) + nestedParam = re.sub(r"^\s*\"(.+)\".*$", "\\1", sInputStr) self.lNestedParams.append(nestedParam) - elif re.match("^\s*\"\s*" + nestedPattern + "\"\s*,*\]*}*\s*$", sInputStr.lower()): - nestedParam = re.sub("^\s*\"(.+)\"\s*.*$", "\\1", sInputStr) + elif re.match(r"^\s*\"\s*" + nestedPattern + r"\"[\s,\]}]*$", sInputStr, re.UNICODE): + nestedParam = re.sub(r"^\s*\"(.+)\".*$", "\\1", sInputStr) self.lNestedParams.append(nestedParam) sInputStr = sInputStr.replace(nestedParam, nestedParam + CNameMangling.STRINGCONVERT.value) - elif re.match("\s*{*\[*\".+\"\s*", sInputStr.lower()) and sInputStr.count("\"")==2 \ - and re.search("(" + nestedPattern + ")*", sInputStr.lower()): - dictPattern = "\[+\s*'[0-9A-Za-z\.\-_${}\[\]]*'\s*\]+|\[+\s*\d+\s*\]+|\[+\s*\${\s*" + variablePattern + "\s*}\s*\]+" - nestedPattern = "\${\s*" + variablePattern + "(\${\s*" + variablePattern + "\s*})*" + "\s*}(" + dictPattern +")*" - lNestedParam = [] - for item in re.findall("(" + nestedPattern + ")", sInputStr): - if item[0] not in lNestedParam: - lNestedParam.append(item[0]) - lNestedBase = [] - tmpList = [] - for nestedParam in lNestedParam: - if nestedParam.count("${") > 1: - tmpNested = nestedParam - if "[" in tmpNested: - pattern = "\[\s*'*\s*(\${\s*[0-9A-Za-z\.\-_${}\[\]]*\s*})\s*'*\s*\]" - lNestedBase.append(re.findall(pattern, tmpNested)[0]) - for item in re.findall(pattern, tmpNested): - tmpItem = item - while tmpItem.count("${") > 1: - newItem = re.sub("(\${\s*" + variablePattern + "\s*})", "str(\\1)", item) - tmpNested = tmpNested.replace(item, newItem) - item = newItem - tmpItem = re.sub("(str\(.+\))", "", item) - sInputStr = sInputStr.replace(nestedParam, tmpNested) - patternItem = re.sub(r'([$()\[\]])', r'\\\1', item) - tmpNested = re.sub("(" + patternItem + ")", "str(\\1)", tmpNested) - sInputStr = re.sub("(" + patternItem + ")", "str(\\1)", sInputStr) - pattern = re.sub(r'([$()\[\]])', r'\\\1', tmpNested) - sInputStr = re.sub("(" + pattern + ")", "str(\\1)", sInputStr) - tmpList.append("str(" + tmpNested + ")") - else: - pattern = "(\${\s*" + variablePattern + "\s*})" - lNestedBase.append(re.findall(pattern, tmpNested)[0]) - for item in re.findall(pattern, tmpNested): - patternItem = re.sub(r'([$()\[\]])', r'\\\1', item) - tmpNested = re.sub("(" + patternItem + ")", "str(\\1)", tmpNested) - sInputStr = re.sub("(" + patternItem + ")", "str(\\1)", sInputStr) - pattern = re.sub(r'([$()\[\]])', r'\\\1', tmpNested) - sInputStr = re.sub("(" + pattern + ")", "str(\\1)", sInputStr) - tmpList.append("str(" + tmpNested + ")") - else: - tmpList.append("str(" + nestedParam + ")") - nestedBasePattern = re.sub(r'([$()\[\]])', r'\\\1', nestedParam) - nestedBasePattern = nestedBasePattern.replace("{", "\{") - nestedBasePattern = nestedBasePattern.replace("}", "\}") - sInputStr = re.sub("(" + nestedBasePattern + ")", "str(\\1)", sInputStr) - lNestedBase.append(nestedParam) - for nestedBase in lNestedBase: - self.lNestedParams.append(nestedBase) - - sInputStr = __recursiveNestedHandling(sInputStr, tmpList) + elif ((re.match(r"[\s{\[]*\".+\"\s*", sInputStr) and sInputStr.count("\"")==2) \ + or (re.match(r"^\s*\${.+}[,\s]*$", sInputStr) and sInputStr.count("{")==sInputStr.count("}") \ + and not re.search(r"(? str: return sOutput def __checkDotInParamName(self, oJson : dict): - ''' - This is recrusive funtion collects all parameters which contain "." in the name. + """ +This is recrusive funtion collects all parameters which contain "." in the name. -**Args:** - **oJson** (*dict*) - Json object which want to collect all parameter's name contained "." +**Arguments:** + +* ``oJson`` + + / *Condition*: required / *Type*: dict / + + Json object which want to collect all parameter's name contained "." **Returns:** - **None** - ''' + + *no return values* + """ for k, v in oJson.items(): if "." in k and k not in self.lDotInParamName: self.lDotInParamName.append(k) if isinstance(v, dict): self.__checkDotInParamName(v) + def __checkNestedParam(self, sInput : str) -> bool: + """ +Checks nested parameter format. + +**Arguments:** + +* ``sInput`` + + / *Condition*: required / *Type*: str / + +**Returns:** + + *raise exception if nested parameter format invalid* + """ + pattern1 = rf"\${{[^{re.escape(self.specialCharacters)}]+\['*.+'*\].*}}" + if re.search(pattern1, sInput, re.UNICODE): + if CNameMangling.STRINGCONVERT.value in sInput: + sInput = sInput.replace(CNameMangling.STRINGCONVERT.value, "") + raise Exception(f"Invalid nested parameter format: '{sInput}'." + "The '[]' or '[]' have to \ +be outside of '${}'.") + else: + return True + + def __changeDictKey(self, dInput : dict, sOldKey : str, sNewKey : str) -> dict: + """ +Replace an existing key in a dictionary with a new key name. The replacement is done by preserving the original order of the keys. + +**Arguments:** + +* ``dInput`` + + / *Condition*: required / *Type*: dict / + +* ``sOldKey`` + + / *Condition*: required / *Type*: str / + +* ``sNewKey`` + + / *Condition*: required / *Type*: str / + +**Returns:** + +* ``dOutput`` + + / *Type*: dict / + """ + listKeys = list(dInput.keys()) + index = listKeys.index(sOldKey) + listKeys.insert(index, sNewKey) + listKeys.pop(index + 1) + dOutput = {} + for key in listKeys: + dOutput[key] = dInput[sOldKey] if key==sNewKey else dInput[key] + return dOutput + def jsonLoad(self, jFile : str, masterFile : bool = True): - ''' - This function is the entry point of JsonPreprocessor. + """ +This method is the entry point of JsonPreprocessor. - It loads the json file, preprocesses it and returns the preprocessed result as data structure. +``jsonLoad`` loads the JSON file, preprocesses it and returns the preprocessed result as Python dictionary. -**Args:** +**Arguments:** - **jFile** (*string*) +* ``jFile`` - Relative/absolute path to main json file. + / *Condition*: required / *Type*: str / - ``%envvariable%`` and ``${envvariable}`` can be used, too in order to access environment variables. + Path and name of main JSON file. The path can be absolute or relative and is also allowed to contain environment variables. **Returns:** - **oJson** (*dict*) +* ``oJson`` - Preprocessed json file(s) as dictionary data structure - ''' - + / *Type*: dict / + + Preprocessed JSON file(s) as Python dictionary + """ def __handleListElements(sInput : str) -> str: items = re.split("\s*,\s*", sInput) j=0 @@ -1031,79 +1051,43 @@ def __handleListElements(sInput : str) -> str: return newItem def __handleDuplicatedKey(dInput : dict) -> dict: - pattern = "\${\s*[0-9A-Za-z_]+[0-9A-Za-z\.\-_]*\s*}(\[+\s*'[0-9A-Za-z\._]+'\s*\]+|\[+\s*\d+\s*\]+)*" + listKeys = list(dInput.keys()) + dictValues = {} + for key in listKeys: + if CNameMangling.DUPLICATEDKEY_01.value in key: + origKey = re.sub(CNameMangling.DUPLICATEDKEY_01.value + "\d+\s*$", "", key) + dictValues[origKey] = copy.deepcopy(dInput[origKey]) + for key in dictValues.keys(): + dInput = self.__changeDictKey(dInput, key, key + CNameMangling.DUPLICATEDKEY_01.value + "00") tmpDict = copy.deepcopy(dInput) for k, v in tmpDict.items(): + if CNameMangling.DUPLICATEDKEY_01.value in k: + origK = re.sub(CNameMangling.DUPLICATEDKEY_01.value + "\d+\s*$", "", k) + dInput[k] = dictValues[origK].pop(1) if isinstance(v, list) and v[0]==CNameMangling.DUPLICATEDKEY_01.value: - i=1 - dupKey = '' - if re.search(pattern, k): - varPattern = "[0-9A-Za-z_]+[0-9A-Za-z\-_]*" - dupKey = re.search("\.(" + varPattern + ")\s*}\s*$|\['(" + varPattern + ")'\]\s*$", k)[1] - if dupKey != '' and dupKey is not None: - tmpKey = dupKey - else: - tmpKey = k - tmpPattern = "\${\s*" + tmpKey + "\s*}|\${\s*" + tmpKey + "\.|\[\s*'"+ tmpKey + "'\s*\]|\." + tmpKey + "\.*" - if not re.search(pattern, str(v[-1])) or not re.search(tmpPattern, str(v[-1])): - dInput[k] = v[-1] - continue - while i < len(v): - bRecursiveKey = False - if re.search(pattern, str(v[i])) and i>1: - if isinstance(v[i], str): - if re.search(tmpPattern, v[i]) or tmpKey in v[i]: - v[i] = re.sub(tmpKey, tmpKey + CNameMangling.DUPLICATEDKEY_02.value + str(i-1), v[i]) - bRecursiveKey = True - if isinstance(v[i], list): - newList = [] - for item in v[i]: - if re.search(tmpPattern, item) or tmpKey in item: - item = re.sub(tmpKey, tmpKey + CNameMangling.DUPLICATEDKEY_02.value + str(i-1), item) - bRecursiveKey = True - newList.append(item) - v[i] = newList - del newList - if bRecursiveKey: - if dupKey == '': - k1 = k + CNameMangling.DUPLICATEDKEY_02.value + str(i) - else: - k1 = re.sub(dupKey, dupKey + CNameMangling.DUPLICATEDKEY_02.value + str(i), k) - dInput[k1] = v[i] - else: - if dupKey == '': - k1 = k + CNameMangling.DUPLICATEDKEY_02.value + str(i) - else: - k1 = re.sub(dupKey, dupKey + CNameMangling.DUPLICATEDKEY_02.value + str(i), k) - dInput[k1] = v[i] - i+=1 - if dupKey != '' and CNameMangling.DUPLICATEDKEY_02.value in str(v): - del dInput[k] - k = re.sub(dupKey, dupKey + CNameMangling.DUPLICATEDKEY_00.value, k) - dInput[k] = v[1] if len(v)==2 else v + v = v[-1] + dInput[k] = v if isinstance(v, dict): dInput[k] = __handleDuplicatedKey(v) del tmpDict + del dictValues return dInput def __removeDuplicatedKey(dInput : dict) -> dict: if isinstance(dInput, dict): for k, v in list(dInput.items()): - if CNameMangling.DUPLICATEDKEY_02.value in k: - del dInput[k] - else: - __removeDuplicatedKey(v) + __removeDuplicatedKey(v) elif isinstance(dInput, list): for item in dInput: __removeDuplicatedKey(item) def __checkKeynameFormat(oJson : dict): - ''' - This function checks a validation of key name in Json configuration file. - ''' - pattern1 = "\${\s*[0-9A-Za-z_]+[0-9A-Za-z\.\-_]*\['*.+'*\]\s*}" + """ +This function checks key names in JSON configuration files. + """ + pattern1 = rf"\${{\s*[^{re.escape(self.specialCharacters)}]*\['*.+'*\]\s*}}" for k, v in oJson.items(): - if re.search(pattern1, k): + if re.search(pattern1, k, re.UNICODE): errorMsg = f"Invalid syntax: Found index or sub-element inside curly brackets in the parameter '{k}'" self.__reset(bCleanGlobalVars=True) raise Exception(errorMsg) @@ -1123,7 +1107,7 @@ def __checkKeynameFormat(oJson : dict): self.__reset(bCleanGlobalVars=True) raise Exception(f"Could not read json file '{jFile}' due to: '{reason}'!") - pattern = "\${\s*[0-9A-Za-z_]+[0-9A-Za-z\.\-_]*\s*}(\[+\s*'.+'\s*\]+|\[+\s*\d+\s*\]+)*" + pattern = rf"\${{\s*[^{re.escape(self.specialCharacters)}]*\s*}}(\[+\s*'.+'\s*\]+|\[+\s*\d+\s*\]+)*" sJsonDataUpdated = "" for line in sJsonData.splitlines(): if line == '' or line.isspace(): @@ -1134,15 +1118,15 @@ def __checkKeynameFormat(oJson : dict): self.__reset(bCleanGlobalVars=True) raise Exception(f"\n{str(error)} in line: '{line}'") - if re.search(pattern, line): - lNestedVar = re.findall("\${\s*([0-9A-Za-z_]+[0-9A-Za-z\.\-_]*)\s*}", line) + if re.search(pattern, line, re.UNICODE): + lNestedVar = re.findall(rf"\${{\s*([^{re.escape(self.specialCharacters)}]+)\s*}}", line, re.UNICODE) for nestedVar in lNestedVar: if nestedVar[0].isdigit(): self.__reset(bCleanGlobalVars=True) raise Exception(f"Invalid parameter format in line: {line.strip()}") - tmpList = re.findall("(\"[^\"]+\")", line) - line = re.sub("(\"[^\"]+\")", CNameMangling.COLONS.value, line) - indexPattern = "\[\s*-*\d*\s*:\s*-*\d*\s*\]" + tmpList = re.findall(r"(\"[^\"]+\")", line) + line = re.sub(r"(\"[^\"]+\")", CNameMangling.COLONS.value, line) + indexPattern = r"\[\s*-*\d*\s*:\s*-*\d*\s*\]" if re.search(indexPattern, line): indexList = re.findall(indexPattern, line) line = re.sub("(" + indexPattern + ")", CNameMangling.LISTINDEX.value, line) @@ -1151,7 +1135,7 @@ def __checkKeynameFormat(oJson : dict): i=0 for item in items: nestedKey = False - nestedKeyPattern = "^\s*,\s*\${.+[\]}]\s*$" + nestedKeyPattern = r"^\s*,\s*\${.+[\]}]\s*$" if i==0 or re.match(nestedKeyPattern, item): nestedKey = True if CNameMangling.COLONS.value in item: @@ -1164,28 +1148,33 @@ def __checkKeynameFormat(oJson : dict): indexList.pop(0) i+=1 newSubItem = "" - if re.search("^\s*\[.+\]\s*,*\s*$", item) and item.count('[')==item.count(']'): + if re.search(r"^\s*\[.+\][\s,]*$", item) and item.count('[')==item.count(']'): item = item.strip() bLastElement = True if item.endswith(","): bLastElement = False - item = re.sub("^\[", "", item) - item = re.sub("\s*\]\s*,*$", "", item) + item = re.sub(r"^\[", "", item) + item = re.sub(r"\s*\][\s,]*$", "", item) newSubItem = __handleListElements(item) newSubItem = "[" + newSubItem + "]" if bLastElement else "[" + newSubItem + "]," - elif re.search("^\s*\[.*\${.+", item): + elif re.search(r"^\s*\[.*\${.+", item): item = item.strip() - item = re.sub("^\[", "", item) + item = re.sub(r"^\[", "", item) newSubItem = __handleListElements(item) newSubItem = "[" + newSubItem - elif re.search("]\s*,*\s*", item) and item.count('[') < item.count(']'): + elif re.search(r"]\s*,*\s*", item) and item.count('[') < item.count(']'): item = item.rstrip() bLastElement = True if item.endswith(","): bLastElement = False - item = re.sub("\s*\]\s*,*$", "", item) + item = re.sub(r"\s*\][\s,]*$", "", item) newSubItem = __handleListElements(item) newSubItem = newSubItem + "]" if bLastElement else newSubItem + "]," + elif re.match(r"^[\s\"]*\${.+[}\]]+[\"\s]*,[\s\"]*\${.+[}\]]+[\"\s]*$", item): + subItems = re.split("\s*,\s*", item) + subItem1 = self.__checkAndUpdateKeyValue(subItems[0], nestedKey) + subItem2 = self.__checkAndUpdateKeyValue(subItems[1], nestedKey=True) + newSubItem = subItem1 + ", " + subItem2 else: newSubItem = self.__checkAndUpdateKeyValue(item, nestedKey) if i str: - ''' - This function writes the content of a Python dictionary to a file in JSON format and returns a normalized path to this JSON file. + """ +This method writes the content of a Python dictionary to a file in JSON format and returns a normalized path to this JSON file. -**Args:** +**Arguments:** - **oJson** (*dictionary*) +* ``oJson`` - Json/Dictionary object. + / *Condition*: required / *Type*: dict / - **outFile** (*string*) +* ``outFile`` (*string*) - Path and name of the JSON output file. The path can be absolute or relative and is also allowed to contain environment variables. + / *Condition*: required / *Type*: str / + + Path and name of the JSON output file. The path can be absolute or relative and is also allowed to contain environment variables. **Returns:** - **outFile** (*string*) +* ``outFile`` (*string*) - Normalized path and name of the JSON output file. - ''' + / *Type*: str / + Normalized path and name of the JSON output file. + """ outFile = CString.NormalizePath(outFile, sReferencePathAbs=os.path.dirname(os.path.abspath(sys.argv[0]))) jsonObject = json.dumps(oJson, ensure_ascii=False, indent=4) try: diff --git a/JsonPreprocessor/JsonPreprocessor.pdf b/JsonPreprocessor/JsonPreprocessor.pdf index fd5849bd..4aa26fec 100644 Binary files a/JsonPreprocessor/JsonPreprocessor.pdf and b/JsonPreprocessor/JsonPreprocessor.pdf differ diff --git a/atest/jsonpreprocessor/test_jsonpreprocessor.py b/atest/jsonpreprocessor/test_jsonpreprocessor.py index a8d3004f..d56efcdb 100644 --- a/atest/jsonpreprocessor/test_jsonpreprocessor.py +++ b/atest/jsonpreprocessor/test_jsonpreprocessor.py @@ -249,12 +249,9 @@ def test_sub_data_structure_01(self): ''' Updated 1 parameter without nested variable. ''' - sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/sub_data_structure_01.jsonp") oJsonPreprocessor = CJsonPreprocessor(syntax="python") + sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_01.jsonp") oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) - sUpdateJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_01.jsonp") - oUpdateJsonData = oJsonPreprocessor.jsonLoad(sUpdateJsonfile) - oJsonData.update(oUpdateJsonData) assert oJsonData['preprocessor']['definitions']['preproFloatParam'] == SUBDATASTRUCTURE['testcase_01'] @@ -262,12 +259,9 @@ def test_sub_data_structure_02(self): ''' Updated more than 1 parameter without nested variable. ''' - sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/sub_data_structure_01.jsonp") oJsonPreprocessor = CJsonPreprocessor(syntax="python") + sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_02.jsonp") oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) - sUpdateJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_02.jsonp") - oUpdateJsonData = oJsonPreprocessor.jsonLoad(sUpdateJsonfile) - oJsonData.update(oUpdateJsonData) assert oJsonData['params']['glo']['globalString'] == SUBDATASTRUCTURE['testcase_02a'] assert oJsonData['preprocessor']['definitions']['preproStructure']['variable_01'] == SUBDATASTRUCTURE['testcase_02b'] @@ -277,12 +271,9 @@ def test_sub_data_structure_03(self): ''' Updated 1 parameter with nested variable in element name. ''' - sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/sub_data_structure_01.jsonp") oJsonPreprocessor = CJsonPreprocessor(syntax="python") + sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_03.jsonp") oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) - sUpdateJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_03.jsonp") - oUpdateJsonData = oJsonPreprocessor.jsonLoad(sUpdateJsonfile) - oJsonData.update(oUpdateJsonData) assert oJsonData['preprocessor']['definitions']['preproStructure']['general'] == SUBDATASTRUCTURE['testcase_03'] @@ -290,12 +281,9 @@ def test_sub_data_structure_04(self): ''' Updated 1 parameter with nested variable in element value. ''' - sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/sub_data_structure_01.jsonp") oJsonPreprocessor = CJsonPreprocessor(syntax="python") + sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_04.jsonp") oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) - sUpdateJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_04.jsonp") - oUpdateJsonData = oJsonPreprocessor.jsonLoad(sUpdateJsonfile) - oJsonData.update(oUpdateJsonData) assert oJsonData['params']['glo']['globalString'] == SUBDATASTRUCTURE['testcase_04'] @@ -303,12 +291,9 @@ def test_sub_data_structure_05(self): ''' Updated 1 parameter with nested variable in both element name and value. ''' - sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/sub_data_structure_01.jsonp") oJsonPreprocessor = CJsonPreprocessor(syntax="python") + sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_05.jsonp") oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) - sUpdateJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_05.jsonp") - oUpdateJsonData = oJsonPreprocessor.jsonLoad(sUpdateJsonfile) - oJsonData.update(oUpdateJsonData) assert oJsonData['preprocessor']['definitions']['preproTest']['checkParam'] == SUBDATASTRUCTURE['testcase_05'] @@ -316,12 +301,9 @@ def test_sub_data_structure_06(self): ''' Updated more than 1 parameter with nested variable. ''' - sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/sub_data_structure_01.jsonp") oJsonPreprocessor = CJsonPreprocessor(syntax="python") + sJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_06.jsonp") oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) - sUpdateJsonfile = os.path.abspath("../testdata/config/05_sub_datastructure/json_update_06.jsonp") - oUpdateJsonData = oJsonPreprocessor.jsonLoad(sUpdateJsonfile) - oJsonData.update(oUpdateJsonData) assert oJsonData['sWelcome'] == SUBDATASTRUCTURE['testcase_06a'] assert oJsonData['params']['glo'] == SUBDATASTRUCTURE['testcase_06b'] diff --git a/atest/testdata/config/05_sub_datastructure/json_update_01.jsonp b/atest/testdata/config/05_sub_datastructure/json_update_01.jsonp index 8732d65e..1dac58a0 100644 --- a/atest/testdata/config/05_sub_datastructure/json_update_01.jsonp +++ b/atest/testdata/config/05_sub_datastructure/json_update_01.jsonp @@ -14,5 +14,6 @@ //************************************************************************** { + "[import]" : "./sub_data_structure_01.jsonp", ${preprocessor}['definitions']['preproFloatParam']: 1999 } diff --git a/atest/testdata/config/05_sub_datastructure/json_update_02.jsonp b/atest/testdata/config/05_sub_datastructure/json_update_02.jsonp index b8e373c5..1ca57e98 100644 --- a/atest/testdata/config/05_sub_datastructure/json_update_02.jsonp +++ b/atest/testdata/config/05_sub_datastructure/json_update_02.jsonp @@ -14,6 +14,7 @@ //************************************************************************** { + "[import]" : "./sub_data_structure_01.jsonp", ${params}['glo']['globalString']: "Welcome to Jsonpreprocessor Acceptance Test", ${preprocessor}['definitions']['preproStructure']['variable_01']: 0.192, ${Project}: "Acceptance Testing" diff --git a/atest/testdata/config/05_sub_datastructure/json_update_03.jsonp b/atest/testdata/config/05_sub_datastructure/json_update_03.jsonp index 32657a88..abac6f49 100644 --- a/atest/testdata/config/05_sub_datastructure/json_update_03.jsonp +++ b/atest/testdata/config/05_sub_datastructure/json_update_03.jsonp @@ -14,6 +14,7 @@ //************************************************************************** { + "[import]" : "./sub_data_structure_01.jsonp", ${preprocessor}['definitions']['preproStructure'][${params}['glo']['globalStructure']['general']]: 0.92 } diff --git a/atest/testdata/config/05_sub_datastructure/json_update_04.jsonp b/atest/testdata/config/05_sub_datastructure/json_update_04.jsonp index 36d98296..867c3373 100644 --- a/atest/testdata/config/05_sub_datastructure/json_update_04.jsonp +++ b/atest/testdata/config/05_sub_datastructure/json_update_04.jsonp @@ -14,5 +14,6 @@ //************************************************************************** { + "[import]" : "./sub_data_structure_01.jsonp", ${params}['glo']['globalString']: ${preprocessor}['definitions']['preproTest']['checkParam'] } diff --git a/atest/testdata/config/05_sub_datastructure/json_update_05.jsonp b/atest/testdata/config/05_sub_datastructure/json_update_05.jsonp index c4fe7484..d952989f 100644 --- a/atest/testdata/config/05_sub_datastructure/json_update_05.jsonp +++ b/atest/testdata/config/05_sub_datastructure/json_update_05.jsonp @@ -14,5 +14,6 @@ //************************************************************************** { + "[import]" : "./sub_data_structure_01.jsonp", ${preprocessor}['definitions']['preproTest'][${preprocessor}['definitions']['preproStructure']['variable_02']]: ${params}['glo']['globalString'] } diff --git a/atest/testdata/config/05_sub_datastructure/json_update_06.jsonp b/atest/testdata/config/05_sub_datastructure/json_update_06.jsonp index 39435ae4..52cbd534 100644 --- a/atest/testdata/config/05_sub_datastructure/json_update_06.jsonp +++ b/atest/testdata/config/05_sub_datastructure/json_update_06.jsonp @@ -14,6 +14,7 @@ //************************************************************************** { + "[import]" : "./sub_data_structure_01.jsonp", ${sWelcome}: ${Project}, ${params}['glo']['globalIntParam']: 69, "params": { diff --git a/test/JPP_TestUsecases.csv b/test/JPP_TestUsecases.csv index 5bf2f164..128e0e46 100644 --- a/test/JPP_TestUsecases.csv +++ b/test/JPP_TestUsecases.csv @@ -75,10 +75,15 @@ JPP_0510|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file containing several parameter a JPP_0511|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file containing a list; list index is defined by a parameter JPP_0512|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file containing a nested use of lists and dictionaries, with the same parameter used several times within the same expression JPP_0513|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file containing several square bracket expressions (as list index and dictionary key) with and without single quotes +JPP_0514|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file containing nested dollar operator expressions +JPP_0515|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file containing nested dollar operator expressions +JPP_0516|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file containing string expressions with additional curly brackets and dollar characters (that must not cause syntax issues!) JPP_0550|COMPOSITE_EXPRESSIONS|BADCASE|JSON file with composite data structure (nested lists and dictionaries / some key names with dots inside) JPP_0551|COMPOSITE_EXPRESSIONS|BADCASE|JSON file containing a list; list index is defined by a parameter and wrapped in single quotes JPP_0552|COMPOSITE_EXPRESSIONS|BADCASE|JSON file containing a list; list index is defined by a parameter and placed inside the curly brackets (invalid syntax) JPP_0553|COMPOSITE_EXPRESSIONS|BADCASE|JSON file containing a list; list index is defined by a parameter, wrapped in single quotes and placed inside the curly brackets (invalid syntax) +JPP_0554|COMPOSITE_EXPRESSIONS|BADCASE|JSON file containing a dictionary; the dictionary key is defined by a parameter and placed inside the curly brackets (invalid syntax) +JPP_0555|COMPOSITE_EXPRESSIONS|BADCASE|JSON file containing a dictionary; the dictionary key is defined by a parameter, wrapped in single quotes and placed inside the curly brackets (invalid syntax) JPP_0600|CODE_COMMENTS|GOODCASE|JSON file with several combinations of code comments JPP_0950|COMMON_SYNTAX_VIOLATIONS|BADCASE|JSON file with syntax error (1) JPP_0951|COMMON_SYNTAX_VIOLATIONS|BADCASE|JSON file with syntax error (2) @@ -88,6 +93,7 @@ JPP_0954|COMMON_SYNTAX_VIOLATIONS|BADCASE|JSON file with syntax error (5): file JPP_1000|IMPLICIT_CREATION|GOODCASE|JSON file with dictionary keys to be created implicitly JPP_1001|IMPLICIT_CREATION|GOODCASE|JSON file with dictionary keys to be created implicitly (same key names at all levels) JPP_1002|IMPLICIT_CREATION|GOODCASE|JSON file with combinations of implicit and explicit creation / with and without initialization +JPP_1003|IMPLICIT_CREATION|GOODCASE|JSON file with combinations of implicit and explicit creation / access to implicitly created keys by parameters / dict assignment by reference JPP_1050|IMPLICIT_CREATION|BADCASE|JSON file with implicit creation of data structures based on parameters (1) JPP_1051|IMPLICIT_CREATION|BADCASE|JSON file with implicit creation of data structures based on parameters (2) JPP_1052|IMPLICIT_CREATION|BADCASE|JSON file with implicit creation of data structures based on parameters (3) diff --git a/test/JPP_TestUsecases.html b/test/JPP_TestUsecases.html index 87519d94..0d6a24ac 100644 --- a/test/JPP_TestUsecases.html +++ b/test/JPP_TestUsecases.html @@ -2539,6 +2539,105 @@ 76 + + + +JPP_0514 + + + + +COMPOSITE_EXPRESSIONS + + + + +GOODCASE + + + + +JSON file containing nested dollar operator expressions
+Expected: JsonPreprocessor returns expected value + + +
+ + + + + + + +77 + + + + + +JPP_0515 + + + + +COMPOSITE_EXPRESSIONS + + + + +GOODCASE + + + + +JSON file containing nested dollar operator expressions
+Expected: JsonPreprocessor returns expected value + + +
+ + + + + + + +78 + + + + + +JPP_0516 + + + + +COMPOSITE_EXPRESSIONS + + + + +GOODCASE + + + + +JSON file containing string expressions with additional curly brackets and dollar characters (that must not cause syntax issues!)
+Expected: JsonPreprocessor returns expected value + + +
+ + + + + + + +79 + + @@ -2570,7 +2669,7 @@ -77 +80 @@ -2604,7 +2703,7 @@ -78 +81 @@ -2637,7 +2736,7 @@ -79 +82 @@ -2670,7 +2769,73 @@ -80 +83 + + + + + +JPP_0554 + + + + +COMPOSITE_EXPRESSIONS + + + + +BADCASE + + + + +JSON file containing a dictionary; the dictionary key is defined by a parameter and placed inside the curly brackets (invalid syntax)
+Expected: No values are returned, and JsonPreprocessor throws an exception + + +
+ + + + + + + +84 + + + + + +JPP_0555 + + + + +COMPOSITE_EXPRESSIONS + + + + +BADCASE + + + + +JSON file containing a dictionary; the dictionary key is defined by a parameter, wrapped in single quotes and placed inside the curly brackets (invalid syntax)
+Expected: No values are returned, and JsonPreprocessor throws an exception + + +
+ + + + + + + +85 @@ -2703,7 +2868,7 @@ -81 +86 @@ -2736,7 +2901,7 @@ -82 +87 @@ -2769,7 +2934,7 @@ -83 +88 @@ -2802,7 +2967,7 @@ -84 +89 @@ -2835,7 +3000,7 @@ -85 +90 @@ -2868,7 +3033,7 @@ -86 +91 @@ -2901,7 +3066,7 @@ -87 +92 @@ -2934,7 +3099,7 @@ -88 +93 @@ -2967,7 +3132,40 @@ -89 +94 + + + + + +JPP_1003 + + + + +IMPLICIT_CREATION + + + + +GOODCASE + + + + +JSON file with combinations of implicit and explicit creation / access to implicitly created keys by parameters / dict assignment by reference
+Expected: JsonPreprocessor returns values + + +
+ + + + + + + +95 @@ -3000,7 +3198,7 @@ -90 +96 @@ -3033,7 +3231,7 @@ -91 +97 @@ -3066,7 +3264,7 @@ -92 +98 @@ -3099,7 +3297,7 @@ -93 +99 @@ -3132,7 +3330,7 @@ -94 +100 @@ -3165,7 +3363,7 @@ -95 +101 @@ -3198,7 +3396,7 @@ -96 +102 @@ -3231,7 +3429,7 @@ -97 +103 @@ -3264,7 +3462,7 @@ -98 +104 @@ -3297,7 +3495,7 @@ -99 +105 @@ -3330,7 +3528,7 @@ -100 +106 @@ -3364,7 +3562,7 @@
 

-
Generated: 24.01.2024 - 13:56:16
+
Generated: 07.03.2024 - 14:58:30
 
diff --git a/test/JPP_TestUsecases.rst b/test/JPP_TestUsecases.rst index adf38a52..6df52fa5 100644 --- a/test/JPP_TestUsecases.rst +++ b/test/JPP_TestUsecases.rst @@ -825,6 +825,36 @@ Test Use Cases ---- +* **Test JPP_0514** + + [COMPOSITE_EXPRESSIONS / GOODCASE] + + **JSON file containing nested dollar operator expressions** + + Expected: JsonPreprocessor returns expected value + +---- + +* **Test JPP_0515** + + [COMPOSITE_EXPRESSIONS / GOODCASE] + + **JSON file containing nested dollar operator expressions** + + Expected: JsonPreprocessor returns expected value + +---- + +* **Test JPP_0516** + + [COMPOSITE_EXPRESSIONS / GOODCASE] + + **JSON file containing string expressions with additional curly brackets and dollar characters (that must not cause syntax issues!)** + + Expected: JsonPreprocessor returns expected value + +---- + * **Test JPP_0550** [COMPOSITE_EXPRESSIONS / BADCASE] @@ -869,6 +899,26 @@ Test Use Cases ---- +* **Test JPP_0554** + + [COMPOSITE_EXPRESSIONS / BADCASE] + + **JSON file containing a dictionary; the dictionary key is defined by a parameter and placed inside the curly brackets (invalid syntax)** + + Expected: No values are returned, and JsonPreprocessor throws an exception + +---- + +* **Test JPP_0555** + + [COMPOSITE_EXPRESSIONS / BADCASE] + + **JSON file containing a dictionary; the dictionary key is defined by a parameter, wrapped in single quotes and placed inside the curly brackets (invalid syntax)** + + Expected: No values are returned, and JsonPreprocessor throws an exception + +---- + * **Test JPP_0600** [CODE_COMMENTS / GOODCASE] @@ -959,6 +1009,16 @@ Test Use Cases ---- +* **Test JPP_1003** + + [IMPLICIT_CREATION / GOODCASE] + + **JSON file with combinations of implicit and explicit creation / access to implicitly created keys by parameters / dict assignment by reference** + + Expected: JsonPreprocessor returns values + +---- + * **Test JPP_1050** [IMPLICIT_CREATION / BADCASE] @@ -1081,5 +1141,5 @@ Test Use Cases ---- -Generated: 24.01.2024 - 13:56:16 +Generated: 07.03.2024 - 14:58:30 diff --git a/test/JPP_TestUsecases.txt b/test/JPP_TestUsecases.txt index da3890e1..cbb9dd45 100644 --- a/test/JPP_TestUsecases.txt +++ b/test/JPP_TestUsecases.txt @@ -345,6 +345,18 @@ Test JPP_0513 / COMPOSITE_EXPRESSIONS / GOODCASE Description: JSON file containing several square bracket expressions (as list index and dictionary key) with and without single quotes Expectation: JsonPreprocessor returns expected value ------------------------------------------------------------------------------------------------------------------------ +Test JPP_0514 / COMPOSITE_EXPRESSIONS / GOODCASE +Description: JSON file containing nested dollar operator expressions +Expectation: JsonPreprocessor returns expected value +------------------------------------------------------------------------------------------------------------------------ +Test JPP_0515 / COMPOSITE_EXPRESSIONS / GOODCASE +Description: JSON file containing nested dollar operator expressions +Expectation: JsonPreprocessor returns expected value +------------------------------------------------------------------------------------------------------------------------ +Test JPP_0516 / COMPOSITE_EXPRESSIONS / GOODCASE +Description: JSON file containing string expressions with additional curly brackets and dollar characters (that must not cause syntax issues!) +Expectation: JsonPreprocessor returns expected value +------------------------------------------------------------------------------------------------------------------------ Test JPP_0550 / COMPOSITE_EXPRESSIONS / BADCASE Description: JSON file with composite data structure (nested lists and dictionaries / some key names with dots inside) Expectation: No values are returned, and JsonPreprocessor throws an exception @@ -363,6 +375,14 @@ Test JPP_0553 / COMPOSITE_EXPRESSIONS / BADCASE Description: JSON file containing a list; list index is defined by a parameter, wrapped in single quotes and placed inside the curly brackets (invalid syntax) Expectation: No values are returned, and JsonPreprocessor throws an exception ------------------------------------------------------------------------------------------------------------------------ +Test JPP_0554 / COMPOSITE_EXPRESSIONS / BADCASE +Description: JSON file containing a dictionary; the dictionary key is defined by a parameter and placed inside the curly brackets (invalid syntax) +Expectation: No values are returned, and JsonPreprocessor throws an exception +------------------------------------------------------------------------------------------------------------------------ +Test JPP_0555 / COMPOSITE_EXPRESSIONS / BADCASE +Description: JSON file containing a dictionary; the dictionary key is defined by a parameter, wrapped in single quotes and placed inside the curly brackets (invalid syntax) +Expectation: No values are returned, and JsonPreprocessor throws an exception +------------------------------------------------------------------------------------------------------------------------ Test JPP_0600 / CODE_COMMENTS / GOODCASE Description: JSON file with several combinations of code comments Expectation: JsonPreprocessor returns remaining content of JSON file (valid parameters) @@ -399,6 +419,10 @@ Test JPP_1002 / IMPLICIT_CREATION / GOODCASE Description: JSON file with combinations of implicit and explicit creation / with and without initialization Expectation: JsonPreprocessor returns values ------------------------------------------------------------------------------------------------------------------------ +Test JPP_1003 / IMPLICIT_CREATION / GOODCASE +Description: JSON file with combinations of implicit and explicit creation / access to implicitly created keys by parameters / dict assignment by reference +Expectation: JsonPreprocessor returns values +------------------------------------------------------------------------------------------------------------------------ Test JPP_1050 / IMPLICIT_CREATION / BADCASE Description: JSON file with implicit creation of data structures based on parameters (1) Expectation: No values are returned, and JsonPreprocessor throws an exception @@ -448,5 +472,5 @@ Description: Relative path to JSON file Expectation: JsonPreprocessor resolves the relative path and returns values from JSON file Hint.......: Works with raw path to JSON file (path not normalized internally) ------------------------------------------------------------------------------------------------------------------------ -Generated: 24.01.2024 - 13:56:16 +Generated: 07.03.2024 - 14:58:30 diff --git a/test/component_test.py b/test/component_test.py index 522f072a..07c4b497 100644 --- a/test/component_test.py +++ b/test/component_test.py @@ -22,8 +22,8 @@ # # -------------------------------------------------------------------------------------------------------------- # -VERSION = "0.25.0" -VERSION_DATE = "24.01.2024" +VERSION = "0.28.0" +VERSION_DATE = "07.03.2024" # # -------------------------------------------------------------------------------------------------------------- #TM*** diff --git a/test/component_test_single.bat b/test/component_test_single.bat new file mode 100644 index 00000000..4e0db40c --- /dev/null +++ b/test/component_test_single.bat @@ -0,0 +1,40 @@ +@echo off + +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0353;JPP_0354;JPP_0355;JPP_0356;JPP_0357;JPP_0358;JPP_0359;JPP_0360" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0361;JPP_0362;JPP_0363;JPP_0364;JPP_0365;JPP_0366" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0367;JPP_0368;JPP_0369;JPP_0370;JPP_0371" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0450;JPP_0451;JPP_0452;JPP_0453;JPP_0454;JPP_0455;JPP_0456;JPP_0457;JPP_0458" + +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0550;JPP_0900" --recreateinstance + + +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0004" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0268" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0450" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0504" + +"%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0515" + + + +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0507" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0508" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0509" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_1150" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0201;JPP_0900" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0201;JPP_0900" --recreateinstance + +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0457;JPP_0458;JPP_0500;JPP_0501" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --testid="JPP_0450;JPP_0457;JPP_0458;JPP_0500;JPP_0501" + + + + +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --codedump +REM "%RobotPythonPath%/python.exe" "D:/ROBFW/components/python-jsonpreprocessor/test/component_test.py" --configdump + +echo --------------------------------------- +echo component_test returned ERRORLEVEL : %ERRORLEVEL% +echo --------------------------------------- + diff --git a/test/pytest/pytestfiles/test_07_COMPOSITE_EXPRESSIONS_GOODCASE.py b/test/pytest/pytestfiles/test_07_COMPOSITE_EXPRESSIONS_GOODCASE.py index 39b14554..7f7e18bc 100644 --- a/test/pytest/pytestfiles/test_07_COMPOSITE_EXPRESSIONS_GOODCASE.py +++ b/test/pytest/pytestfiles/test_07_COMPOSITE_EXPRESSIONS_GOODCASE.py @@ -18,7 +18,7 @@ # # XC-CT/ECA3-Queckenstedt # -# 15.01.2024 - 16:29:04 +# 07.03.2024 - 14:58:30 # # -------------------------------------------------------------------------------------------------------------- @@ -133,4 +133,28 @@ def test_JPP_0512(self, Description): def test_JPP_0513(self, Description): nReturn = CExecute.Execute("JPP_0513") assert nReturn == 0 +# -------------------------------------------------------------------------------------------------------------- + # Expected: JsonPreprocessor returns expected value + @pytest.mark.parametrize( + "Description", ["JSON file containing nested dollar operator expressions",] + ) + def test_JPP_0514(self, Description): + nReturn = CExecute.Execute("JPP_0514") + assert nReturn == 0 +# -------------------------------------------------------------------------------------------------------------- + # Expected: JsonPreprocessor returns expected value + @pytest.mark.parametrize( + "Description", ["JSON file containing nested dollar operator expressions",] + ) + def test_JPP_0515(self, Description): + nReturn = CExecute.Execute("JPP_0515") + assert nReturn == 0 +# -------------------------------------------------------------------------------------------------------------- + # Expected: JsonPreprocessor returns expected value + @pytest.mark.parametrize( + "Description", ["JSON file containing string expressions with additional curly brackets and dollar characters (that must not cause syntax issues!)",] + ) + def test_JPP_0516(self, Description): + nReturn = CExecute.Execute("JPP_0516") + assert nReturn == 0 # -------------------------------------------------------------------------------------------------------------- diff --git a/test/pytest/pytestfiles/test_08_COMPOSITE_EXPRESSIONS_BADCASE.py b/test/pytest/pytestfiles/test_08_COMPOSITE_EXPRESSIONS_BADCASE.py index ef966733..f3762099 100644 --- a/test/pytest/pytestfiles/test_08_COMPOSITE_EXPRESSIONS_BADCASE.py +++ b/test/pytest/pytestfiles/test_08_COMPOSITE_EXPRESSIONS_BADCASE.py @@ -18,7 +18,7 @@ # # XC-CT/ECA3-Queckenstedt # -# 24.01.2024 - 13:56:16 +# 06.03.2024 - 15:51:02 # # -------------------------------------------------------------------------------------------------------------- @@ -61,4 +61,20 @@ def test_JPP_0552(self, Description): def test_JPP_0553(self, Description): nReturn = CExecute.Execute("JPP_0553") assert nReturn == 0 +# -------------------------------------------------------------------------------------------------------------- + # Expected: No values are returned, and JsonPreprocessor throws an exception + @pytest.mark.parametrize( + "Description", ["JSON file containing a dictionary; the dictionary key is defined by a parameter and placed inside the curly brackets (invalid syntax)",] + ) + def test_JPP_0554(self, Description): + nReturn = CExecute.Execute("JPP_0554") + assert nReturn == 0 +# -------------------------------------------------------------------------------------------------------------- + # Expected: No values are returned, and JsonPreprocessor throws an exception + @pytest.mark.parametrize( + "Description", ["JSON file containing a dictionary; the dictionary key is defined by a parameter, wrapped in single quotes and placed inside the curly brackets (invalid syntax)",] + ) + def test_JPP_0555(self, Description): + nReturn = CExecute.Execute("JPP_0555") + assert nReturn == 0 # -------------------------------------------------------------------------------------------------------------- diff --git a/test/pytest/pytestfiles/test_11_IMPLICIT_CREATION_GOODCASE.py b/test/pytest/pytestfiles/test_11_IMPLICIT_CREATION_GOODCASE.py index 3e708064..996c5720 100644 --- a/test/pytest/pytestfiles/test_11_IMPLICIT_CREATION_GOODCASE.py +++ b/test/pytest/pytestfiles/test_11_IMPLICIT_CREATION_GOODCASE.py @@ -18,7 +18,7 @@ # # XC-CT/ECA3-Queckenstedt # -# 16.01.2024 - 13:22:08 +# 07.03.2024 - 14:58:30 # # -------------------------------------------------------------------------------------------------------------- @@ -53,4 +53,12 @@ def test_JPP_1001(self, Description): def test_JPP_1002(self, Description): nReturn = CExecute.Execute("JPP_1002") assert nReturn == 0 +# -------------------------------------------------------------------------------------------------------------- + # Expected: JsonPreprocessor returns values + @pytest.mark.parametrize( + "Description", ["JSON file with combinations of implicit and explicit creation / access to implicitly created keys by parameters / dict assignment by reference",] + ) + def test_JPP_1003(self, Description): + nReturn = CExecute.Execute("JPP_1003") + assert nReturn == 0 # -------------------------------------------------------------------------------------------------------------- diff --git a/test/testconfig/TestConfig.py b/test/testconfig/TestConfig.py index 809c2af7..221df1ba 100644 --- a/test/testconfig/TestConfig.py +++ b/test/testconfig/TestConfig.py @@ -22,7 +22,7 @@ # # -------------------------------------------------------------------------------------------------------------- # -# 24.01.2024 +# 07.03.2024 # # !!! Temporarily tests are deactivated by the following line commented out: # # # listofdictUsecases.append(dictUsecase) @@ -1855,7 +1855,7 @@ dictUsecase['SUBSECTION'] = "GOODCASE" dictUsecase['HINT'] = None dictUsecase['COMMENT'] = None -dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_0504.jsonp" +dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_0504.jsonp" # current issue: Expecting value: line 20 column 14 (char 1088)'! dictUsecase['EXPECTEDEXCEPTION'] = None dictUsecase['EXPECTEDRETURN'] = """ [DICT] (20/1) > {param1} [DICT] (1/1) > {A} [DICT] (1/1) > {B} [DICT] (1/1) > {C} [DICT] (1/1) > {D} [FLOAT] : 1.23 @@ -2075,6 +2075,78 @@ listofdictUsecases.append(dictUsecase) del dictUsecase # -------------------------------------------------------------------------------------------------------------- +dictUsecase = {} +dictUsecase['TESTID'] = "JPP_0514" +dictUsecase['DESCRIPTION'] = "JSON file containing nested dollar operator expressions" +dictUsecase['EXPECTATION'] = "JsonPreprocessor returns expected value" +dictUsecase['SECTION'] = "COMPOSITE_EXPRESSIONS" +dictUsecase['SUBSECTION'] = "GOODCASE" +dictUsecase['HINT'] = None +dictUsecase['COMMENT'] = None +dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_0514.jsonp" +dictUsecase['EXPECTEDEXCEPTION'] = None +dictUsecase['EXPECTEDRETURN'] = """ +[DICT] (5/1) > {keyP} [STR] : 'A' +[DICT] (5/2) > {B} [INT] : 1 +[DICT] (5/3) > {dictP} [DICT] (2/1) > {A} [STR] : 'B' +[DICT] (5/3) > {dictP} [DICT] (2/2) > {C} [INT] : 2 +[DICT] (5/4) > {newparam_1} [STR] : '1' +[DICT] (5/5) > {newparam_2} [STR] : '1'""" +listofdictUsecases.append(dictUsecase) +del dictUsecase +# -------------------------------------------------------------------------------------------------------------- +dictUsecase = {} +dictUsecase['TESTID'] = "JPP_0515" +dictUsecase['DESCRIPTION'] = "JSON file containing nested dollar operator expressions" +dictUsecase['EXPECTATION'] = "JsonPreprocessor returns expected value" +dictUsecase['SECTION'] = "COMPOSITE_EXPRESSIONS" +dictUsecase['SUBSECTION'] = "GOODCASE" +dictUsecase['HINT'] = None +dictUsecase['COMMENT'] = None +dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_0515.jsonp" +dictUsecase['EXPECTEDEXCEPTION'] = None +dictUsecase['EXPECTEDRETURN'] = """ +[DICT] (12/1) > {keyP} [STR] : 'A' +[DICT] (12/2) > {B} [STR] : 'keyP' +[DICT] (12/3) > {dictP} [DICT] (1/1) > {A} [STR] : 'B' +[DICT] (12/4) > {newparam_1} [STR] : 'B' +[DICT] (12/5) > {newparam_2} [STR] : 'keyP' +[DICT] (12/6) > {newparam_3} [STR] : 'A' +[DICT] (12/7) > {newparam_4} [STR] : 'B' +[DICT] (12/8) > {newparam_5} [STR] : 'keyP' +[DICT] (12/9) > {newparam_6} [STR] : 'A' +[DICT] (12/10) > {newparam_7} [STR] : 'B' +[DICT] (12/11) > {newparam_8} [STR] : 'keyP' +[DICT] (12/12) > {newparam_9} [STR] : 'A'""" +listofdictUsecases.append(dictUsecase) +del dictUsecase +# -------------------------------------------------------------------------------------------------------------- +dictUsecase = {} +dictUsecase['TESTID'] = "JPP_0516" +dictUsecase['DESCRIPTION'] = "JSON file containing string expressions with additional curly brackets and dollar characters (that must not cause syntax issues!)" +dictUsecase['EXPECTATION'] = "JsonPreprocessor returns expected value" +dictUsecase['SECTION'] = "COMPOSITE_EXPRESSIONS" +dictUsecase['SUBSECTION'] = "GOODCASE" +dictUsecase['HINT'] = None +dictUsecase['COMMENT'] = None +dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_0516.jsonp" +dictUsecase['EXPECTEDEXCEPTION'] = None +dictUsecase['EXPECTEDRETURN'] = """ +[DICT] (9/1) > {listparam} [LIST] (3/1) > [STR] : 'A' +[DICT] (9/1) > {listparam} [LIST] (3/2) > [STR] : 'B' +[DICT] (9/1) > {listparam} [LIST] (3/3) > [STR] : 'C' +[DICT] (9/2) > {param_1} [STR] : '}A{' +[DICT] (9/3) > {param_2} [STR] : '{A}' +[DICT] (9/4) > {param_3} [STR] : '$}A$}' +[DICT] (9/5) > {param_4} [STR] : '{$}A{$}' +[DICT] (9/6) > {param_5} [STR] : '}{$}A{$}{' +[DICT] (9/7) > {param_6} [STR] : '{}{$}A{$}{}' +[DICT] (9/8) > {param_7} [STR] : '{}A{$}B{$}C{}' +[DICT] (9/9) > {param_8} [STR] : '{}$A{$$}$B{$$}$C{}' +""" +listofdictUsecases.append(dictUsecase) +del dictUsecase +# -------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------- dictUsecase = {} dictUsecase['TESTID'] = "JPP_0550" @@ -2132,6 +2204,34 @@ listofdictUsecases.append(dictUsecase) del dictUsecase # -------------------------------------------------------------------------------------------------------------- +dictUsecase = {} +dictUsecase['TESTID'] = "JPP_0554" +dictUsecase['DESCRIPTION'] = "JSON file containing a dictionary; the dictionary key is defined by a parameter and placed inside the curly brackets (invalid syntax)" +dictUsecase['EXPECTATION'] = "No values are returned, and JsonPreprocessor throws an exception" +dictUsecase['SECTION'] = "COMPOSITE_EXPRESSIONS" +dictUsecase['SUBSECTION'] = "BADCASE" +dictUsecase['HINT'] = None +dictUsecase['COMMENT'] = None +dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_0554.jsonp" +dictUsecase['EXPECTEDEXCEPTION'] = "Invalid nested parameter format" +dictUsecase['EXPECTEDRETURN'] = None +listofdictUsecases.append(dictUsecase) +del dictUsecase +# -------------------------------------------------------------------------------------------------------------- +dictUsecase = {} +dictUsecase['TESTID'] = "JPP_0555" +dictUsecase['DESCRIPTION'] = "JSON file containing a dictionary; the dictionary key is defined by a parameter, wrapped in single quotes and placed inside the curly brackets (invalid syntax)" +dictUsecase['EXPECTATION'] = "No values are returned, and JsonPreprocessor throws an exception" +dictUsecase['SECTION'] = "COMPOSITE_EXPRESSIONS" +dictUsecase['SUBSECTION'] = "BADCASE" +dictUsecase['HINT'] = None +dictUsecase['COMMENT'] = None +dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_0555.jsonp" +dictUsecase['EXPECTEDEXCEPTION'] = "Invalid nested parameter format" +dictUsecase['EXPECTEDRETURN'] = None +listofdictUsecases.append(dictUsecase) +del dictUsecase +# -------------------------------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------------------------------- dictUsecase = {} dictUsecase['TESTID'] = "JPP_0600" @@ -2309,7 +2409,31 @@ [DICT] (5/4) > {testdict_2} [DICT] (1/1) > {subKey} [DICT] (1/1) > {subKey} [DICT] (1/1) > {paramA} [DICT] (3/1) > {B} [INT] : 2 [DICT] (5/4) > {testdict_2} [DICT] (1/1) > {subKey} [DICT] (1/1) > {subKey} [DICT] (1/1) > {paramA} [DICT] (3/2) > {paramB} [DICT] (1/1) > {C} [INT] : 3 [DICT] (5/4) > {testdict_2} [DICT] (1/1) > {subKey} [DICT] (1/1) > {subKey} [DICT] (1/1) > {paramA} [DICT] (3/3) > {paramC} [DICT] (1/1) > {D} [INT] : 4 -[DICT] (5/5) > {testdict_3} [DICT] (1/1) > {paramD} [DICT] (1/1) > {paramE} [DICT] (1/1) > {paramD} [DICT] (1/1) > {E} [DICT] (1/1) > {F} [INT] : 6""" +[DICT] (5/5) > {testdict_3} [DICT] (1/1) > {paramD} [DICT] (1/1) > {paramE} [DICT] (1/1) > {paramD} [DICT] (1/1) > {E} [DICT] (1/1) > {F} [INT] : 6 +""" +listofdictUsecases.append(dictUsecase) +del dictUsecase +# -------------------------------------------------------------------------------------------------------------- +dictUsecase = {} +# ====== still one line in jpp-test_config_1003.jsonp commented out; remaining code run properly +dictUsecase['TESTID'] = "JPP_1003" +dictUsecase['DESCRIPTION'] = "JSON file with combinations of implicit and explicit creation / access to implicitly created keys by parameters / dict assignment by reference" +dictUsecase['EXPECTATION'] = "JsonPreprocessor returns values" +dictUsecase['SECTION'] = "IMPLICIT_CREATION" +dictUsecase['SUBSECTION'] = "GOODCASE" +dictUsecase['HINT'] = None +dictUsecase['COMMENT'] = None +dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_1003.jsonp" +dictUsecase['EXPECTEDEXCEPTION'] = None +dictUsecase['EXPECTEDRETURN'] = """ +[DICT] (7/1) > {testdict2} [DICT] (1/1) > {subKey1} [DICT] (1/1) > {subKey2} [DICT] (1/1) > {subKey3} [DICT] (1/1) > {subKey4} [INT] : 3 +[DICT] (7/2) > {param1} [STR] : 'subKey1' +[DICT] (7/3) > {param2} [STR] : 'subKey2' +[DICT] (7/4) > {param3} [STR] : 'subKey3' +[DICT] (7/5) > {param4} [STR] : 'subKey4' +[DICT] (7/6) > {param5} [INT] : 3 +[DICT] (7/7) > {testdict1} [DICT] (1/1) > {subKey1} [DICT] (1/1) > {subKey2} [DICT] (1/1) > {subKey3} [DICT] (1/1) > {subKey4} [INT] : 3 +""" listofdictUsecases.append(dictUsecase) del dictUsecase # -------------------------------------------------------------------------------------------------------------- @@ -2365,7 +2489,7 @@ dictUsecase['HINT'] = None dictUsecase['COMMENT'] = None dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_1053.jsonp" -dictUsecase['EXPECTEDEXCEPTION'] = "Only simple data types are allowed to be substituted inside" +dictUsecase['EXPECTEDEXCEPTION'] = "only simple data types are allowed to be substituted inside" dictUsecase['EXPECTEDRETURN'] = None listofdictUsecases.append(dictUsecase) del dictUsecase @@ -2379,7 +2503,7 @@ dictUsecase['HINT'] = None dictUsecase['COMMENT'] = None dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_1054.jsonp" -dictUsecase['EXPECTEDEXCEPTION'] = "Only simple data types are allowed to be substituted inside" +dictUsecase['EXPECTEDEXCEPTION'] = "only simple data types are allowed to be substituted inside" dictUsecase['EXPECTEDRETURN'] = None listofdictUsecases.append(dictUsecase) del dictUsecase @@ -2393,7 +2517,7 @@ dictUsecase['HINT'] = None dictUsecase['COMMENT'] = None dictUsecase['JSONFILE'] = r"..\testfiles\jpp-test_config_1055.jsonp" -dictUsecase['EXPECTEDEXCEPTION'] = "Only simple data types are allowed to be substituted inside" +dictUsecase['EXPECTEDEXCEPTION'] = "only simple data types are allowed to be substituted inside" dictUsecase['EXPECTEDRETURN'] = None listofdictUsecases.append(dictUsecase) del dictUsecase diff --git a/test/testfiles/jpp-test_config_0514.jsonp b/test/testfiles/jpp-test_config_0514.jsonp new file mode 100644 index 00000000..da90182c --- /dev/null +++ b/test/testfiles/jpp-test_config_0514.jsonp @@ -0,0 +1,22 @@ +// Copyright 2020-2023 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//************************************************************************** +{ + "keyP" : "A", + "B" : 1, + "dictP" : {"A" : "B", "C" : 2}, + "newparam_1" : "${${dictP}[${keyP}]}", + "newparam_2" : "${${dictP}['${keyP}']}" +} + diff --git a/test/testfiles/jpp-test_config_0515.jsonp b/test/testfiles/jpp-test_config_0515.jsonp new file mode 100644 index 00000000..14251b52 --- /dev/null +++ b/test/testfiles/jpp-test_config_0515.jsonp @@ -0,0 +1,30 @@ +// Copyright 2020-2023 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//************************************************************************** +{ + "keyP" : "A", + "B" : "keyP", + "dictP" : {"A" : "B"}, + // + "newparam_1" : "${dictP}['${keyP}']", // => "${dictP}['A']" -> 'B' + "newparam_2" : "${${dictP}['${keyP}']}", // => "${B}" -> 'keyP' + "newparam_3" : "${${${dictP}['${keyP}']}}", // => "${keyP}" -> 'A' + "newparam_4" : "${dictP}['${${${dictP}['${keyP}']}}']", // => "${dictP}['A']" -> 'B' + "newparam_5" : "${${dictP}['${${${dictP}['${keyP}']}}']}", // => "${B}" -> 'keyP' + "newparam_6" : "${${${dictP}['${${${dictP}['${keyP}']}}']}}", // => "${keyP}" -> 'A' + "newparam_7" : "${dictP}['${${${dictP}['${${${dictP}['${keyP}']}}']}}']", // => "${dictP}['A']" -> 'B' + "newparam_8" : "${${dictP}['${${${dictP}['${${${dictP}['${keyP}']}}']}}']}", // => "${B}" -> 'keyP' + "newparam_9" : "${${${dictP}['${${${dictP}['${${${dictP}['${keyP}']}}']}}']}}" // => "${keyP}" -> 'A' +} + diff --git a/test/testfiles/jpp-test_config_0516.jsonp b/test/testfiles/jpp-test_config_0516.jsonp new file mode 100644 index 00000000..7ce4a176 --- /dev/null +++ b/test/testfiles/jpp-test_config_0516.jsonp @@ -0,0 +1,26 @@ +// Copyright 2020-2023 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//************************************************************************** +{ + "listparam" : ["A","B","C"], + "param_1" : "}${listparam}[0]{", + "param_2" : "{${listparam}[0]}", + "param_3" : "$}${listparam}[0]$}", + "param_4" : "{$}${listparam}[0]{$}", + "param_5" : "}{$}${listparam}[0]{$}{", + "param_6" : "{}{$}${listparam}[0]{$}{}", + "param_7" : "{}${listparam}[0]{$}${listparam}[1]{$}${listparam}[2]{}", + "param_8" : "{}$${listparam}[0]{$$}$${listparam}[1]{$$}$${listparam}[2]{}" +} + diff --git a/test/testfiles/jpp-test_config_0956.jsonp b/test/testfiles/jpp-test_config_0554.jsonp similarity index 87% rename from test/testfiles/jpp-test_config_0956.jsonp rename to test/testfiles/jpp-test_config_0554.jsonp index ce198816..06271955 100644 --- a/test/testfiles/jpp-test_config_0956.jsonp +++ b/test/testfiles/jpp-test_config_0554.jsonp @@ -13,6 +13,7 @@ // limitations under the License. //************************************************************************** { - "param1" : "value", - "param2" : ${param1}[0] + "keyP" : "A", + "dictP" : {"A" : "B", "C" : 2}, + "newparam" : "${dictP[${keyP}]}" } diff --git a/test/testfiles/jpp-test_config_0555.jsonp b/test/testfiles/jpp-test_config_0555.jsonp new file mode 100644 index 00000000..b634cb51 --- /dev/null +++ b/test/testfiles/jpp-test_config_0555.jsonp @@ -0,0 +1,19 @@ +// Copyright 2020-2023 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//************************************************************************** +{ + "keyP" : "A", + "dictP" : {"A" : "B", "C" : 2}, + "newparam" : "${dictP['${keyP}']}" +} diff --git a/test/testfiles/jpp-test_config_1002.jsonp b/test/testfiles/jpp-test_config_1002.jsonp index 079052fb..bb9b14a1 100644 --- a/test/testfiles/jpp-test_config_1002.jsonp +++ b/test/testfiles/jpp-test_config_1002.jsonp @@ -33,9 +33,6 @@ ${testdict_3.paramD.paramE.paramD} : {"E" : 5}, // // usage of parameters allowed only in case of a key with this name already exists - - - ${testdict_3.paramD.paramE.paramD}[${paramE}] : {"F" : 6} // ${paramE} is accepted because the value is "E" and a key with this name already exists } diff --git a/test/testfiles/jpp-test_config_1003.jsonp b/test/testfiles/jpp-test_config_1003.jsonp new file mode 100644 index 00000000..b0c5715a --- /dev/null +++ b/test/testfiles/jpp-test_config_1003.jsonp @@ -0,0 +1,37 @@ +// Copyright 2020-2023 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//************************************************************************** + +{ + // implicitly created data structure + ${testdict1.subKey1.subKey2.subKey3} : {"subKey4" : 1}, + "testdict2" : ${testdict1}, // by reference + // + // parameters containing names of existing keys + "param1" : "subKey1", + "param2" : "subKey2", + "param3" : "subKey3", + "param4" : "subKey4", + // + // access to implicitly created keys by parameters (standard notation); all are strings, therefore single quotes do not matter + ${testdict1}[${param1}]['${param2}']['subKey3'][${param4}] : 2, + // access to implicitly created keys by parameters (dotdict notation) + ${testdict2.${param1}.subKey2.${param3}.subKey4} : 3, + // assign modified values to new parameters + "param5" : ${testdict1}[${param1}]['${param2}']['subKey3'][${param4}] + + // still issue: + // "param6" : ${testdict2.${param1}.subKey2.${param3}.subKey4} // Expecting value: line 11 column 15 (char 412)'! +} +