diff --git a/JsonPreprocessor/CJsonPreprocessor.py b/JsonPreprocessor/CJsonPreprocessor.py index 12149d1c..8322a923 100644 --- a/JsonPreprocessor/CJsonPreprocessor.py +++ b/JsonPreprocessor/CJsonPreprocessor.py @@ -71,6 +71,7 @@ class CNameMangling(Enum): LISTINDEX = "__IndexOfList__" SLICEINDEX = "__SlicingIndex__" STRINGVALUE = "__StringValueMake-up__" + DYNAMICIMPORTED = "__DynamicImportedHandling__" class CPythonJSONDecoder(json.JSONDecoder): """ @@ -190,18 +191,20 @@ def __init__(self, syntax: CSyntaxType = CSyntaxType.python , currentCfg : dict self.jsonPath = None self.masterFile = None self.handlingFile = [] + self.iDynamicImport = 0 + self.lDynamicImports = [] self.lImportedFiles = [] self.recursive_level = 0 self.syntax = syntax self.currentCfg = currentCfg self.dUpdatedParams = {} self.lDotInParamName = [] - self.bDuplicatedKeys = True + self.bJSONPreCheck = False self.jsonCheck = {} self.JPGlobals = {} - self.pythonTypeError = ["object is not subscriptable", \ - "string indices must be integers", \ - "list indices must be integers", \ + self.pythonTypeError = ["object is not subscriptable", + "string indices must be integers", + "list indices must be integers", "index out of range"] def __getFailedJsonDoc(self, jsonDecodeError=None, areaBeforePosition=50, areaAfterPosition=20, oneLine=True): @@ -232,11 +235,13 @@ def __reset(self) -> None: self.jsonPath = None self.masterFile = None self.handlingFile = [] + self.iDynamicImport = 0 + self.lDynamicImports = [] self.lImportedFiles = [] self.recursive_level = 0 self.dUpdatedParams = {} self.lDotInParamName = [] - self.bDuplicatedKeys = True + self.bJSONPreCheck = False self.jsonCheck = {} self.JPGlobals = {} @@ -267,31 +272,62 @@ def __processImportFiles(self, input_data : dict) -> dict: sCheckElement = CNameMangling.DUPLICATEDKEY_01.value for key, value in input_data: if re.match('^\s*\[\s*import\s*\]\s*', key.lower()): - currJsonPath = self.jsonPath - abs_path_file = CString.NormalizePath(value, sReferencePathAbs = currJsonPath) - - # Use recursive_level and lImportedFiles to avoid cyclic import - self.recursive_level = self.recursive_level + 1 # increase recursive level - - # length of lImportedFiles should equal to recursive_level - self.lImportedFiles = self.lImportedFiles[:self.recursive_level] if self.masterFile is not None else \ - self.lImportedFiles[:self.recursive_level-1] - if abs_path_file in self.lImportedFiles: - raise Exception(f"Cyclic imported json file '{abs_path_file}'!") - - oJsonImport = self.jsonLoad(abs_path_file) - self.jsonPath = currJsonPath - tmpOutdict = copy.deepcopy(out_dict) - for k1, v1 in tmpOutdict.items(): - for k2, v2 in oJsonImport.items(): - if k2 == k1: - del out_dict[k1] - del tmpOutdict - out_dict.update(oJsonImport) - - self.recursive_level = self.recursive_level - 1 # descrease recursive level + if not isinstance(value, str): + errorMsg = f"The value of [import] parameter must be 'str' but receiving the value '{value}'" + self.__reset() + raise Exception(errorMsg) + if '${' in value: + if not self.bJSONPreCheck: # self.bJSONPreCheck is set True when handling pre-check JSON files by __preCheckJsonFile() + value = self.lDynamicImports.pop(0) + if '${' in value: + dynamicImported = re.search(rf'^(.*){CNameMangling.DYNAMICIMPORTED.value}(.*)$', value) + value = self.__removeTokenStr(dynamicImported[2]) + nestedParams = re.findall(rf'(\${{[^{re.escape(self.specialCharacters)}]+}}(\[.*\])*)', value) + sParams = '' + for item in nestedParams: + sParams += f"{item[0]} " + errorMsg = f"Could not load the import file '{value}'. The parameter '{sParams}' is not available!" + self.__reset() + raise Exception(errorMsg) + else: + if re.match(r'^\[\s*import\s*\]$', key.strip()): + self.iDynamicImport +=1 + value = self.jsonPath + CNameMangling.DYNAMICIMPORTED.value + value + out_dict[f"{key.strip()}_{self.iDynamicImport}"] = value + self.lDynamicImports.append(value) + else: + out_dict[key] = value + if '${' not in value: + if re.match(r'^\[\s*import\s*\]_\d+$', key): + if value in self.lDynamicImports: + raise Exception(f"Cyclic imported json file '{value}'!") + dynamicIpmportIndex = re.search(r'_(\d+)$', key)[1] + self.lDynamicImports[int(dynamicIpmportIndex)-1] = value + currJsonPath = self.jsonPath + abs_path_file = CString.NormalizePath(value, sReferencePathAbs = currJsonPath) + + # Use recursive_level and lImportedFiles to avoid cyclic import + self.recursive_level = self.recursive_level + 1 # increase recursive level + + # length of lImportedFiles should equal to recursive_level + self.lImportedFiles = self.lImportedFiles[:self.recursive_level] if self.masterFile is not None else \ + self.lImportedFiles[:self.recursive_level-1] + if abs_path_file in self.lImportedFiles: + raise Exception(f"Cyclic imported json file '{abs_path_file}'!") + + oJsonImport = self.jsonLoad(abs_path_file) + self.jsonPath = currJsonPath + tmpOutdict = copy.deepcopy(out_dict) + for k1, v1 in tmpOutdict.items(): + for k2, v2 in oJsonImport.items(): + if k2 == k1: + del out_dict[k1] + del tmpOutdict + out_dict.update(oJsonImport) + + self.recursive_level = self.recursive_level - 1 # descrease recursive level else: - if self.bDuplicatedKeys: + if not self.bJSONPreCheck: specialCharacters = r'$[]{}' tmpOutdict = copy.deepcopy(out_dict) for k1, v1 in tmpOutdict.items(): @@ -535,22 +571,27 @@ def __getNestedValue(sNestedParam : str): exec(sExec, locals(), ldict) tmpValue = ldict['value'] except Exception as error: - self.__reset() - sNestedParam = self.__removeTokenStr(sNestedParam) - errorMsg = '' - for errorType in self.pythonTypeError: - if errorType in str(error): - errorMsg = f"Could not resolve expression '{sNestedParam.replace('$$', '$')}'." - if errorMsg != '': - errorMsg = errorMsg + f" Reason: {error}" if ' or slices' not in str(error) else \ - errorMsg + f" Reason: {str(error).replace(' or slices', '')}" + if self.bJSONPreCheck: + sNestedParam = self.__removeTokenStr(sNestedParam) + tmpValue = sNestedParam.replace('$$', '$') + pass else: - if isinstance(error, KeyError) and re.search(r"\[\s*" + str(error) + "\s*\]", sNestedParam): - errorMsg = f"Could not resolve expression '{sNestedParam.replace('$$', '$')}'. \ -Reason: Key error {error}" + self.__reset() + sNestedParam = self.__removeTokenStr(sNestedParam) + errorMsg = '' + for errorType in self.pythonTypeError: + if errorType in str(error): + errorMsg = f"Could not resolve expression '{sNestedParam.replace('$$', '$')}'." + if errorMsg != '': + errorMsg = errorMsg + f" Reason: {error}" if ' or slices' not in str(error) else \ + errorMsg + f" Reason: {str(error).replace(' or slices', '')}" else: - errorMsg = f"The parameter '{sNestedParam.replace('$$', '$')}' is not available!" - raise Exception(errorMsg) + if isinstance(error, KeyError) and re.search(r"\[\s*" + str(error) + "\s*\]", sNestedParam): + errorMsg = f"Could not resolve expression '{sNestedParam.replace('$$', '$')}'. \ +Reason: Key error {error}" + else: + errorMsg = f"The parameter '{sNestedParam.replace('$$', '$')}' is not available!" + raise Exception(errorMsg) return tmpValue specialCharacters = r'[]{}' @@ -560,12 +601,12 @@ def __getNestedValue(sNestedParam : str): for var in referVars: if var not in sInputStr: continue - if "." in var: + if re.search(r'\${.+\..+}', var): sVar = self.__handleDotInNestedParam(var) sInputStr = sInputStr.replace(var, sVar) tmpPattern = pattern + rf'(\[\s*\d+\s*\]|\[\s*\'[^{re.escape(specialCharacters)}]+\'\s*\])*' sNestedParam = self.__removeTokenStr(sInputStr.replace("$$", "$")) - if "." in sInputStr and not bConvertToStr: + if re.search(r'\${.+\..+}', sInputStr) and not bConvertToStr: sInputStr = self.__handleDotInNestedParam(sInputStr) while re.search(tmpPattern, sInputStr, re.UNICODE) and sInputStr.count("$$")>1: sLoopCheck = sInputStr @@ -573,8 +614,11 @@ def __getNestedValue(sNestedParam : str): if len(referVars)==0: referVars = re.findall(r'(' + tmpPattern + r')$', sInputStr, re.UNICODE) for var in referVars: - sVar = self.__handleDotInNestedParam(var[0]) if "." in var[0] else var[0] + sVar = self.__handleDotInNestedParam(var[0]) if re.search(r'\${.+\..+}', var[0]) else var[0] tmpValue = __getNestedValue(sVar) + if self.bJSONPreCheck: + if "${" in tmpValue and bConvertToStr: + tmpValue = tmpValue + CNameMangling.STRINGCONVERT.value if (isinstance(tmpValue, list) or isinstance(tmpValue, dict)) and bConvertToStr: self.__reset() sVar = self.__removeTokenStr(sVar) @@ -643,12 +687,12 @@ def __getNestedValue(sNestedParam : str): tmpPattern = pattern + rf'(\[\s*\-*\d+\s*\]|\[[\s\']*[^{re.escape(specialCharacters)}]+[\'\s]*\])*' if re.match("^" + tmpPattern + "$", sInputStr.strip(), re.UNICODE) and bKey and not bConvertToStr: rootVar = re.search(pattern, sInputStr, re.UNICODE)[0] - sRootVar = self.__handleDotInNestedParam(rootVar) if "." in rootVar else rootVar + sRootVar = self.__handleDotInNestedParam(rootVar) if re.search(r'\${.+\..+}', rootVar) else rootVar sInputStr = sInputStr.replace(rootVar, sRootVar) return self.__multipleReplace(sInputStr, {"$${":"", "}":""}) var = re.search(tmpPattern, sInputStr, re.UNICODE) if var==None: - sVar = self.__handleDotInNestedParam(sInputStr) if "." in sInputStr else sInputStr + sVar = self.__handleDotInNestedParam(sInputStr) if re.search(r'\${.+\..+}', sInputStr) else sInputStr sVar = re.sub(r'^\s*\$\${\s*([^}]+)}', "['\\1']", sVar) sExec = "value = self.JPGlobals" + sVar try: @@ -656,20 +700,25 @@ def __getNestedValue(sNestedParam : str): exec(sExec, locals(), ldict) tmpValue = ldict['value'] except Exception as error: - self.__reset() - errorMsg = '' - for errorType in self.pythonTypeError: - if errorType in str(error): - errorMsg = f"Could not resolve expression '{sNestedParam.replace('$$', '$')}'." - if errorMsg != '': - errorMsg = errorMsg + f" Reason: {error}" + if self.bJSONPreCheck: + sNestedParam = self.__removeTokenStr(sNestedParam) + tmpValue = sNestedParam.replace('$$', '$') + pass else: - errorMsg = f"The parameter '{sNestedParam.replace('$$', '$')}' is not available!" - raise Exception(errorMsg) + self.__reset() + errorMsg = '' + for errorType in self.pythonTypeError: + if errorType in str(error): + errorMsg = f"Could not resolve expression '{sNestedParam.replace('$$', '$')}'." + if errorMsg != '': + errorMsg = errorMsg + f" Reason: {error}" + else: + errorMsg = f"The parameter '{sNestedParam.replace('$$', '$')}' is not available!" + raise Exception(errorMsg) return tmpValue else: rootVar = re.search(pattern, var[0], re.UNICODE)[0] - sRootVar = self.__handleDotInNestedParam(rootVar) if "." in rootVar else rootVar + sRootVar = self.__handleDotInNestedParam(rootVar) if re.search(r'\${.+\..+}', rootVar) else rootVar sVar = var[0].replace(rootVar, sRootVar) tmpValue = __getNestedValue(sVar) if bConvertToStr and (isinstance(tmpValue, list) or isinstance(tmpValue, dict)): @@ -982,7 +1031,7 @@ def __loadNestedValue(initValue: str, sInputStr: str, bKey=False, key=''): dictPattern = r"(\[+\s*'[^\$\[\]\(\)]+'\s*\]+|\[+\s*\d+\s*\]+|\[+\s*\${\s*[^\[]*\s*}.*\]+)*|" + indexPattern pattern = r"\${\s*[^\[}\$]*(\.*\${\s*[\[]*\s*})*" + dictPattern bValueConvertString = False - if CNameMangling.STRINGCONVERT.value in sInputStr: + if CNameMangling.STRINGCONVERT.value in sInputStr or re.match(r'^\[\s*import\s*\]_\d+$', key): bValueConvertString = True sInputStr = sInputStr.replace(CNameMangling.STRINGCONVERT.value, '') sInputStr = re.sub("\$", "$$", sInputStr) @@ -1148,14 +1197,30 @@ def __handleList(lInput : list, bNested : bool) -> list: initValue = v while isinstance(v, str) and "${" in v: sLoopCheck = v - if v.count('${')==1 and CNameMangling.STRINGCONVERT.value not in v: # re.match(pattern, v) - if '.' in v: + if v.count('${')==1 and CNameMangling.STRINGCONVERT.value not in v: + if re.search(r'\${.+\..+}', v): paramInValue = self.__handleDotInNestedParam(v) paramInValue = self.__multipleReplace(paramInValue, {'${':'', '}':''}) - v = __loadNestedValue(initValue, v) + v = __loadNestedValue(initValue, v, key=k) + # Handle dynamic import value + if re.match(r'^\[\s*import\s*\]_\d+$', k): + if '${' not in v and CNameMangling.DYNAMICIMPORTED.value in v: + dynamicImported = re.search(rf'^(.*){CNameMangling.DYNAMICIMPORTED.value}(.*)$', v) + if re.match(r'^[\d\.]+$', dynamicImported[2]) or \ + re.search(r'(\[[^\[]+\])|(\([^\(]+\))|({[^{]+})', dynamicImported[2]): + errorMsg = f"The value of [import] parameter must be 'str' but receiving the value '{dynamicImported[2]}'" + self.__reset() + raise Exception(errorMsg) + if re.match(r'^[/|\\].+$', dynamicImported[2]): + v = dynamicImported[2] + else: + v = CString.NormalizePath(dynamicImported[2], sReferencePathAbs = dynamicImported[1]) if v == sLoopCheck: - self.__reset() - raise Exception(f"Invalid expression found: '{self.__removeTokenStr(initValue)}'.") + if not self.bJSONPreCheck: + self.__reset() + raise Exception(f"Invalid expression found: '{self.__removeTokenStr(initValue)}'.") + else: + break if isinstance(v, str) and re.search(r'\[[^\]]+\]', v): sExec = 'value = ' + v try: @@ -1252,9 +1317,13 @@ def __checkNestedParam(self, sInput : str, bKey=False, bCheckKeyName=False) -> b pattern = rf"^\${{\s*[^{re.escape(self.specialCharacters)}]+\s*}}(\[.*\])+$" pattern1 = rf"\${{.+}}(\[.+\])*[^\[]*\${{" pattern2 = r"\[[a-zA-Z0-9\.\-\+\${}'\s]*:[a-zA-Z0-9\.\-\+\${}'\s]*\]" # Slicing pattern + if CNameMangling.DYNAMICIMPORTED.value in sInput: + dynamicImported = re.search(rf'^(.*){CNameMangling.DYNAMICIMPORTED.value}(.*)$', sInput) + sInput = dynamicImported[2] # Checks special character in parameters sTmpInput = sInput bSpecialCharInParam = False + sCheckInput = sTmpInput while sTmpInput.count("${") > 1: lParams = re.findall(r'\${([^\$}]*)}', sTmpInput) for param in lParams: @@ -1263,8 +1332,9 @@ def __checkNestedParam(self, sInput : str, bKey=False, bCheckKeyName=False) -> b bSpecialCharInParam = True break sTmpInput = sTmpInput.replace('${' + param + '}', '') - if bSpecialCharInParam: + if bSpecialCharInParam or sCheckInput==sTmpInput: break + sCheckInput = sTmpInput if "${" not in sInput: return True errorMsg = None @@ -1291,7 +1361,8 @@ def __checkNestedParam(self, sInput : str, bKey=False, bCheckKeyName=False) -> b re.match(r"^[\s\"]*\${[^!@#%\^&\*\(\)=|;,<>?/`~]+[\s\"]*$", sInput)): errorMsg = f"Invalid syntax! One or more than one closed curly bracket is missing in \ expression '{self.__removeTokenStr(sInput.strip())}'." - elif not re.match(r"^\${.+[}\]]+$", sInput) or (re.search(pattern1, sInput) and not bKey): + elif (not re.match(r"^\${.+[}\]]+$", sInput) or (re.search(pattern1, sInput) and not bKey)) \ + and not self.bJSONPreCheck: if CNameMangling.STRINGCONVERT.value not in sInput and CNameMangling.DUPLICATEDKEY_01.value not in sInput: sTmpInput = re.sub(r"(\.\${[a-zA-Z0-9\.\_]+}(\[[^\[]+\])*)", "", sInput) if not re.match(r"^\s*\${[a-zA-Z0-9\.\_]+}(\[[^\[]+\])*\s*$", sTmpInput): @@ -1428,6 +1499,56 @@ def __removeTokenStr(self, sInput : str) -> str: sInput = sInput.replace(tokenStr.value, '') return sInput + def __preCheckJsonFile(self, sInput, CJSONDecoder): + ''' +Checks and handle dynamic path of imported file. +**Arguments:** + +* ``sInput`` + + / *Condition*: required / *Type*: str / + +* ``CJSONDecoder`` + + / *Condition*: required / *Type*: str / + +**Returns:** + +* ``sInput`` + + / *Type*: str / + ''' + try: + self.jsonCheck = json.loads(sInput, cls=CJSONDecoder, object_pairs_hook=self.__processImportFiles) + except Exception as error: + failedJsonDoc = self.__getFailedJsonDoc(error) + jsonException = "not defined" + if failedJsonDoc is None: + jsonException = f"{error}\nIn file: '{self.handlingFile.pop(-1)}'" if len(self.handlingFile)>0 else f"{error}" + else: + jsonException = f"{error}\nNearby: '{failedJsonDoc}'\nIn file: '{self.handlingFile.pop(-1)}'" if len(self.handlingFile)>0 else \ + f"{error}\nNearby: '{failedJsonDoc}'" + self.__reset() + raise Exception(jsonException) + self.JPGlobals = self.jsonCheck + importPattern = r'([\'|"]\s*\[\s*import\s*\]_*\d*\s*[\'|"]\s*:\s*[\'|"][^\'"]+[\'|"])' + sJson = json.dumps(self.jsonCheck) + lImport = re.findall(importPattern, sJson) + if len(lImport)==0: + sInput = sJson + return sInput + else: + while re.search(importPattern, sJson): + tmpJson = sJson + self.__checkDotInParamName(self.jsonCheck) + oJson, bNested = self.__updateAndReplaceNestedParam(self.jsonCheck) + sJson = json.dumps(oJson) + if sJson==tmpJson: + break + sJson = self.__preCheckJsonFile(sJson, CJSONDecoder) + sInput = sJson + return sInput + def jsonLoad(self, jFile : str): """ This method is the entry point of JsonPreprocessor. @@ -1518,7 +1639,7 @@ def __handleDuplicatedKey(dInput : dict, parentParams : str = '') -> dict: self.__reset() formatOverwritten1 = re.sub(r'^\[([^\[]+)\]', '${\\1}', parentParams) formatOverwritten1 = formatOverwritten1 + f"['{origK}']" - formatOverwritten2 = self.__multipleReplace(parentParams, {"]['" : ".", "']['" : ".", "[" : "", "']" : ""}) + formatOverwritten2 = self.__multipleReplace(parentParams, {"]['":".", "']['":".", "[":"", "']":"", "]":""}) formatOverwritten2 = "${" + formatOverwritten2 + f".{origK}}}" raise Exception(f"Missing scope for parameter '${{{origK}}}'. To change the value of this parameter, \ an absolute path must be used: '{formatOverwritten1}' or '{formatOverwritten2}'.") @@ -1731,24 +1852,13 @@ def __handleLastElement(sInput : str) -> str: self.__reset() raise Exception(f"Provided syntax '{self.syntax}' is not supported.") # Load the temporary Json object without checking duplicated keys for - # verifying duplicated keys later. + # verifying duplicated keys later. The pre-check method also checks dynamic + # imported files in JSON files. if firstLevel: - self.bDuplicatedKeys = False - try: - self.jsonCheck = json.loads(sJsonDataUpdated, - cls=CJSONDecoder, - object_pairs_hook=self.__processImportFiles) - except Exception as error: - failedJsonDoc = self.__getFailedJsonDoc(error) - jsonException = "not defined" - if failedJsonDoc is None: - jsonException = f"{error}\nIn file: '{self.handlingFile.pop(-1)}'" if len(self.handlingFile)>0 else f"{error}" - else: - jsonException = f"{error}\nNearby: '{failedJsonDoc}'\nIn file: '{self.handlingFile.pop(-1)}'" if len(self.handlingFile)>0 else \ - f"{error}\nNearby: '{failedJsonDoc}'" - self.__reset() - raise Exception(jsonException) - self.bDuplicatedKeys = True + self.bJSONPreCheck = True + sDummyData = self.__preCheckJsonFile(sJsonDataUpdated, CJSONDecoder) + self.iDynamicImport = 0 + self.bJSONPreCheck = False # Load Json object with checking duplicated keys feature is enabled. # The duplicated keys feature uses the self.jsonCheck object to check duplicated keys. diff --git a/JsonPreprocessor/JsonPreprocessor.pdf b/JsonPreprocessor/JsonPreprocessor.pdf index 0495713b..b6c2e7a2 100644 Binary files a/JsonPreprocessor/JsonPreprocessor.pdf and b/JsonPreprocessor/JsonPreprocessor.pdf differ diff --git a/JsonPreprocessor/version.py b/JsonPreprocessor/version.py index 0c8f38a7..0f8f463f 100644 --- a/JsonPreprocessor/version.py +++ b/JsonPreprocessor/version.py @@ -18,6 +18,6 @@ # # Version and date of JsonPreprocessor # -VERSION = "0.8.1" -VERSION_DATE = "28.10.2024" +VERSION = "0.8.2" +VERSION_DATE = "30.10.2024" diff --git a/config/robotframework_aio/release_items_JsonPreprocessor.json b/config/robotframework_aio/release_items_JsonPreprocessor.json index 08fb7da9..aac70b2d 100644 --- a/config/robotframework_aio/release_items_JsonPreprocessor.json +++ b/config/robotframework_aio/release_items_JsonPreprocessor.json @@ -138,7 +138,11 @@ * Added a naming convention check for key names within JSONP content processed by the **JsonPreprocessor** - Key names have to start with a character, digit, or underscore and must not contain these special characters ``!#$%^&()=[]{}|;',?`~`` + Naming convention: + + * Key names can only consist of letters, digits and the following special characters: ``_ + - * / \\`` (backslashes are allowed but must be masked). + * Key names must start with a letter, a digit or an underscore. + * Key names must not be empty strings. But leading and trailing blanks will be removed (and therefore do not cause errors). **Example:** @@ -146,20 +150,30 @@ Invalid key names are: ``\"+param01\"``, ``\"param$01\"``, ``\"abc#Param\"``, ... -* Checked absolute path when overwriting parameter +* Reworked handling of parameter scope - When overwriting a parameter, the absolute path of the parameter must be provided + To change the value of an existing parameter, an absolute path must be used always. **Example:** - |``\u007b`` - | ``\"params\" : \u007b\"001\" : \u007b\"002\" : \u007b`` - | ``\"param\" : 1,`` - | ``$\u007bparams.001.002.param\u007d : 2`` - | ``\u007d`` - | ``\u007d`` - | ``\u007d`` - |``\u007d`` +| ``{`` +| ``\u00a0\u00a0\u00a0\"params\"\u00a0:\u00a0{\"001\"\u00a0:\u00a0{\"002\"\u00a0:\u00a0{`` +| ``\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"param\"\u00a0:\u00a01,`` +| ``\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0${params.001.002.param}\u00a0:\u00a02`` +| ``\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}`` +| ``\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}`` +| ``\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}`` +| ``}`` + +* Enabled dynamic paths for imported JSON files (based on dollar operator expressions) + + **Example:** + +| ``{`` +| ``\u00a0\u00a0\u00a0\"root_folder\" : \"imports\",`` +| ``\u00a0\u00a0\u00a0\"json_file\"\u00a0\u00a0\u00a0: \"configuration.jsonp\",`` +| ``\u00a0\u00a0\u00a0\"[import]\"\u00a0\u00a0\u00a0\u00a0: \"./${root_folder}/${json_file}\" `` +| ``}`` " ] } diff --git a/packagedoc/additional_docs/History.tex b/packagedoc/additional_docs/History.tex index 4a22a006..1effe64b 100644 --- a/packagedoc/additional_docs/History.tex +++ b/packagedoc/additional_docs/History.tex @@ -86,4 +86,7 @@ - Prevented side effects of token string in error messages log\newline - Checked absolute path when overwriting parameter} +\historyversiondate{0.8.2}{10/2024} +\historychange{- Enhanced import JSON file feature which allows dynamic path of imported file} + \end{packagehistory} diff --git a/test/JPP_TestUsecases.csv b/test/JPP_TestUsecases.csv index 52d6dca9..278e4680 100644 --- a/test/JPP_TestUsecases.csv +++ b/test/JPP_TestUsecases.csv @@ -67,6 +67,12 @@ JPP_0369|VALUE_DETECTION|BADCASE|JSON file with expression starting with '${' an JPP_0370|VALUE_DETECTION|BADCASE|JSON file with expression starting with '${' and ending with '}', further matching '${' and '}' in between (not all nested) (invalid syntax 4) JPP_0371|VALUE_DETECTION|BADCASE|JSON file with expression starting with '${' and ending with '}', further matching '${' and '}' in between (not all nested) (invalid syntax 5) JPP_0400|NAMING_CONVENTION|GOODCASE|JSON file with several parameter names w.r.t. the naming convention +JPP_0450|NAMING_CONVENTION|BADCASE|JSON file with several invalid parameter names (1) +JPP_0451|NAMING_CONVENTION|BADCASE|JSON file with several invalid parameter names (2) +JPP_0452|NAMING_CONVENTION|BADCASE|JSON file with several invalid parameter names (3) +JPP_0453|NAMING_CONVENTION|BADCASE|JSON file with several invalid parameter names (4) +JPP_0454|NAMING_CONVENTION|BADCASE|JSON file with several invalid parameter names (5) +JPP_0455|NAMING_CONVENTION|BADCASE|JSON file with several invalid parameter names (6) JPP_0500|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file with composite data structure (nested lists and dictionaries 1) JPP_0501|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file with composite data structure (nested lists and dictionaries 2) JPP_0502|COMPOSITE_EXPRESSIONS|GOODCASE|JSON file with composite data structure (nested lists and dictionaries 3 / some key names with dots inside) @@ -110,8 +116,25 @@ JPP_1055|IMPLICIT_CREATION|BADCASE|JSON file with implicit creation of data stru JPP_1056|IMPLICIT_CREATION|BADCASE|JSON file with implicit creation of data structures based on parameters (6) JPP_1057|IMPLICIT_CREATION|BADCASE|JSON file with implicit creation of data structures based on parameters (7) JPP_1058|IMPLICIT_CREATION|BADCASE|JSON file with implicit creation of data structures based on parameters (8) -JPP_1150|CYCLIC_IMPORTS|BADCASE|JSON file with cyclic imports (JSON file imports itself) -JPP_1151|CYCLIC_IMPORTS|BADCASE|JSON file with cyclic imports (JSON file imports another file, that is already imported) +JPP_1100|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import (1)) +JPP_1101|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import (2)) +JPP_1102|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import (3)) +JPP_1103|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import (4)) +JPP_1104|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import (5)) +JPP_1105|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import (6)) +JPP_1106|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import, recursive (7)) +JPP_1107|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import, alternate (8)) +JPP_1108|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import, parallel (9)) +JPP_1109|FILE_IMPORTS|GOODCASE|JSON file import based on dictionary key values +JPP_1110|FILE_IMPORTS|GOODCASE|JSON file import based on list elemens +JPP_1111|FILE_IMPORTS|GOODCASE|JSON file import based on parameters (dynamic import (7)) +JPP_1150|FILE_IMPORTS|BADCASE|JSON file with cyclic imports (JSON file imports itself, fix path) +JPP_1151|FILE_IMPORTS|BADCASE|JSON file with cyclic imports (JSON file imports another file, that is already imported, fix path) +JPP_1154|FILE_IMPORTS|BADCASE|JSON file with not existing parameter within dynamic import path +JPP_1155|FILE_IMPORTS|BADCASE|JSON file with not existing import file +JPP_1158|FILE_IMPORTS|BADCASE|JSON file with error in [import] key (1) +JPP_1159|FILE_IMPORTS|BADCASE|JSON file with error in [import] key (2) +JPP_1160|FILE_IMPORTS|BADCASE|JSON file with error in imported file JPP_1200|PATH_FORMATS|GOODCASE|Relative path to JSON file JPP_1350|BLOCKED_SLICING|BADCASE|JSON file with blocked slicing notation (-1) JPP_1351|BLOCKED_SLICING|BADCASE|JSON file with blocked slicing notation (-1) @@ -156,11 +179,11 @@ JPP_2001|PARAMETER_SCOPE|GOODCASE|JSON file with nested dictionary, in which a p JPP_2002|PARAMETER_SCOPE|GOODCASE|JSON file with nested dictionary, in which a parameter is overwritten (3) JPP_2003|PARAMETER_SCOPE|GOODCASE|JSON file with nested dictionary, in which a parameter is overwritten (4) JPP_2007|PARAMETER_SCOPE|GOODCASE|JSON file with nested dictionary, in which a parameter is overwritten (8) -JPP_2500|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (1) -JPP_2501|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (2) -JPP_2502|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (3) -JPP_2503|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (4) -JPP_2504|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (5) -JPP_2506|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (7) -JPP_2507|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (8) -JPP_2508|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (9) +JPP_2050|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (1) +JPP_2051|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (2) +JPP_2052|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (3) +JPP_2053|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (4) +JPP_2054|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (5) +JPP_2056|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (7) +JPP_2057|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (8) +JPP_2058|PARAMETER_SCOPE|BADCASE|JSON file containing a parameter with missing scope (9) diff --git a/test/JPP_TestUsecases.html b/test/JPP_TestUsecases.html index 90c0d544..133a4e2b 100644 --- a/test/JPP_TestUsecases.html +++ b/test/JPP_TestUsecases.html @@ -2272,6 +2272,204 @@ 68 +