#!/usr/bin/python # -*- coding: utf-8 -*- # --------------------------------------------------------------------------------------------------------- # Author : Ulrich Carmesin # Source : gitea.ucarmesin.de # --------------------------------------------------------------------------------------------------------- import json import utils.css_tool import utils.report_tool import basic.program import basic.constants as B import utils.match_const as M # ------------------------------------------------------------ """ """ class Matching(): def __init__(self, comp): self.comp = comp self.elements = {} self.matchfiles = {} self.assignedFiles = {} self.linksA = {} self.linksB = {} self.nomatch = {} self.matchkeys = {} self.htmltext = "" self.sideA = [] self.sideB = [] self.mode = "" self.matchtype = "" self.cssClass = "result0" def setData(self, tdata, match): """ selects testdata for actual matching :param tdata: :param match: kind of actual match :return: """ sideA = M.MATCH[match]["A"] sideB = M.MATCH[match]["B"] self.sideA = tdata[sideA]["data"] self.sideB = tdata[sideB]["data"] self.matchfiles["A"] = tdata[sideA]["path"] self.matchfiles["B"] = tdata[sideB]["path"] self.matchtype = match self.mode = M.MATCH[match]["mode"] self.setDiffHeader() self.report = utils.report_tool.Report.getInstance() self.resetHits() def resetHits(self): self.linksA = {} self.linksB = {} self.nomatch = {} self.matchkeys = {} self.cssClass = "result0" def setCssClass(self, cssClass): if cssClass > self.cssClass: self.cssClass = cssClass def isHitA(self, key): return ((key in self.linksA) and (self.linksA[key] != B.SVAL_NULL)) def isHitB(self, key): return ((key in self.linksB) and (self.linksB[key] != B.SVAL_NULL)) def setHit(self, keyA, keyB): if (not self.isHitA(keyA)) and (not self.isHitB(keyB)): if (keyA != B.SVAL_NULL): self.linksA[keyA] = keyB if (keyB != B.SVAL_NULL): self.linksB[keyB] = keyA return "OK" raise Exception("one of the links are set") def setNohit(self, similarity, keyA, keyB): """ The similarity must be unique. Only a mismatch will be set. """ if similarity in self.nomatch: raise Exception("similarity " + similarity + " exists") if (self.isHitA(keyA) or self.isHitB(keyB)): return self.nomatch[similarity] = [keyA, keyB] def getTableDdl(self, path): a = path.split(":") ddl = self.comp.conf[B.DATA_NODE_DDL] for x in a: if (len(x) < 2): continue if (x == B.DATA_NODE_DATA): break if x in ddl: ddl = ddl[x] return ddl def setDiffHeader(matching): job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 1 job.debug(verify, "getDiffHeader ") htmltxt = "" htmltxt += "" htmltxt += "" + M.MATCH[matching.matchtype]["title"] + "" htmltxt += utils.css_tool.getInternalStyle("diffFiles") htmltxt += "" htmltxt += "" htmltxt += "

" + M.MATCH[matching.matchtype]["title"] + "

" htmltxt += "

" + M.MATCH[M.MATCH[matching.matchtype]["A"]]["long"] + ": " + matching.matchfiles[ "A"] + "

" htmltxt += "

" + M.MATCH[M.MATCH[matching.matchtype]["B"]]["long"] + ": " + matching.matchfiles[ "B"] + "


" matching.htmltext = htmltxt def setDiffFooter(self): job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 4 job.debug(verify, "getDiffFooter ") htmltext = self.htmltext htmltext += "" self.htmltext = htmltext def matchFiles(matching): """ post: :param matching: :return: """ def matchBestfit(matching, path): """ in this strategy the difference-score of all elements of both sides will be calculated. the best fit will assigned together until nothing is * the elements can be each kind of object * the count of elements should not be high :param matching: :return: """ # initialize all potential links with null i = 0 if (matching.sideA is not None): for r in matching.sideA: k = composeKey("a", i) matching.setHit(k, B.SVAL_NULL) i += 1 i = 0 if (matching.sideB is not None): for r in matching.sideB: k = composeKey("b", i) matching.setHit(B.SVAL_NULL, k) i += 1 ia = 0 ix = 1 # CASE: one side is None if (matching.sideA is None) or (matching.sideB is None): return # CASE: to match for rA in matching.sideA: ib = 0 for rB in matching.sideB: if (matching.isHitA(composeKey("a", ia))): continue if (matching.isHitB(composeKey("b", ib))): ib += 1 continue similarity = getSimilarity(matching, rA, rB, ix, M.MATCH[matching.matchtype]["simorder"]) if (similarity == "MATCH"): matching.setHit(composeKey("a", ia), composeKey("b", ib)) continue else: matching.setNohit(similarity, composeKey("a", ia), composeKey("b", ib)) ib += 1 ix += 1 ia += 1 def matchRestfit(matching): """ """ job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 1 job.debug(verify, "matchRestfit ") for x in sorted(matching.nomatch, reverse=True): job.debug(verify, "matchRestfit " + x) pair = matching.nomatch[x] if (matching.isHitA(pair[0])): print("A " + pair[0] + " bereits zugeordnet mit " + matching.linksA[pair[0]]) if (matching.isHitB(pair[1])): print("B " + pair[1] + " bereits zugeordnet mit " + matching.linksB[pair[1]]) continue if (matching.isHitA(pair[0])): continue print("neues Matching " + pair[0] + " " + pair[1]) matching.setHit(pair[0], pair[1]) def composeKey(side, i): return side.lower() + str(i + 1).zfill(4) def extractKeyI(key): return int(key[1:]) - 1 def setMatchkeys(matching, path): job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 1 job.debug(verify, "getSimilarity " + path) if len(matching.matchkeys) > 0: return if (B.DATA_NODE_DDL in matching.comp.conf): job.debug(verify, "ddl " + path) a = path.split(":") ddl = matching.comp.conf[B.DATA_NODE_DDL] for x in a: if (len(x) < 2): continue if (x == B.DATA_NODE_DATA): break if x in ddl: ddl = ddl[x] job.debug(verify, "ddl " + json.dumps(ddl)) keys = {} for f in ddl: job.debug(verify, "ddl " + f) if ("key" in ddl[f]) and (len(ddl[f]["key"]) > 0): b = ddl[f]["key"].split(":") if (len(b) != 2): raise Exception("falsch formatierter Schluessel " + ddl[f]["key"]) if (not b[1].isnumeric()): raise Exception("falsch formatierter Schluessel " + ddl[f]["key"]) k = "k"+b[0]+""+b[1].zfill(2) job.debug(verify, "ddl " + f) keys[k] = {"ktyp": b[0], "field": ddl[f]["feld"], "type": ddl[f]["type"], "rule": ddl[f]["acceptance"]} matching.matchkeys = keys job.debug(verify, "ddl " + json.dumps(keys)) def getSimilarity(matching, rA, rB, i, simorder=M.SIM_DEFAULT): """ it calculates the similarity between both rows by: concat each criteria with single-similarity 00..99 and i with 999..000 """ job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 1 job.debug(verify, "getSimilarity ") mBsim = "" mTsim = "" topBsim = "" topTsim = "" for k in sorted(matching.matchkeys): if M.SIM_TECHNICAL in k: mTsim += getStringSimilarity(str(rA[matching.matchkeys[k]["field"]]), str(rB[matching.matchkeys[k]["field"]])) topTsim += "99" if M.SIM_BUSINESS in k: mTsim += getStringSimilarity(str(rA[matching.matchkeys[k]["field"]]), str(rB[matching.matchkeys[k]["field"]])) topTsim += "99" if mBsim == topBsim and mTsim == topTsim: job.debug(verify, "Treffer ") return "MATCH" elif simorder[0:1] == M.SIM_TECHNICAL and mTsim == topTsim: job.debug(verify, "Treffer ") return "MATCH" elif simorder[0:1] == M.SIM_BUSINESS and mBsim == topBsim: job.debug(verify, "Treffer ") return "MATCH" elif simorder[0:1] == M.SIM_TECHNICAL: return "S"+mTsim+mBsim+str(i).zfill(3) else: # if simorder[0:1] == M.SIM_BUSINESS: return "S" + mBsim + mTsim + str(i).zfill(3) def matchTree(matching): """ :param matching: :return: """ job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 4 job.debug(verify, "..>> start matching " + matching.mode) matchElement(matching, matching.sideA, matching.sideB, "") matching.setDiffFooter() job.debug(verify, "..>> ende matching " + matching.htmltext) return matching.htmltext def matchElement(matching, A, B, path): """ travers through the datatree """ job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 4 job.debug(verify, "matchElem " + path + " A " + str(type(A)) + " B " + str(type(B))) if ((A is not None) and (isinstance(A, list))) \ or ((B is not None) and (isinstance(B, list))): return matchArray(matching, A, B, path) elif ((A is not None) and (isinstance(A, dict))) \ or ((B is not None) and (isinstance(B, dict))): return matchDict(matching, A, B, path) else: return matching def getStringSimilarity(strA, strB): job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 1 job.debug(verify, "getStringSimilarity " + strA + " ?= " + strB) if (strA == strB): return "99" if (strA.strip() == strB.strip()): return "77" if (strA.lower() == strB.lower()): return "66" if (strA.strip().lower() == strB.strip().lower()): return "55" return "00" def getEvaluation(matching, type, acceptance, sideA, sideB): job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 1 job.debug(verify, "getEvaluation " + str(sideA) + " ?= " + str(sideB)) match = getStringSimilarity(str(sideA), str(sideB)) classA = "novalue" classB = "novalue" result = "test" if match == "99": return ["MATCH", "novalue", "novalue", "novalue", "novalue"] if acceptance == "ignore": result = "ignore" if (matching.matchtype == M.MATCH_POSTCOND) and (result == "test"): result = "hard" classA = "diffA" classB = "diffB" else: classA = "acceptA" classB = "acceptB" return [result, classA, classB] def matchDict(matching, A, B, path): """ travers through the datatree """ job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 4 job.debug(verify, "matchDict " + path) if (A is not None): for k in A: job.debug(verify, "matchDict 400 " + k + ".") if k in ["_match"]: continue if (B is not None) and (k in B): if (isinstance(A[k], dict)): A[k]["_match"] = "Y" if (isinstance(B[k], dict)): B[k]["_match"] = "Y" job.debug(verify, "matchDict 404 " + k + "." + path) matchElement(matching, A[k], B[k], path + ":" + k) else: if (isinstance(A[k], dict)): A[k]["_match"] = "N" job.debug(verify, "matchDict 408 " + path) matchElement(matching, A[k], None, path + ":" + k) if (B is not None): for k in B: job.debug(verify, "matchDict 412 " + k + ".") if k in ["_match"]: continue if (A is not None) and (k in A): continue elif (A is None) or (k not in A): if (A is not None) and (isinstance(A[k], dict)): B[k]["_match"] = "N" job.debug(verify, "matchDict 418 " + k + "." + path) matchElement(matching, None, B[k], path + ":" + k) job.debug(verify, "matchDict 420 ...<<---") return matching def matchArray(matching, A, B, path): """ matches the datarows of the datatree """ job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 4 job.debug(verify, "matchArray " + path + "\n.." + matching.htmltext) matching.sideA = A matching.sideB = B matchBestfit(matching, path) matchRestfit(matching) a = path.split(":") for x in a: if (x == "_data"): break table = x report = utils.report_tool.Report.getInstance() report.setPaths(getattr(job.par, "testcase"), matching.comp.name, table, matching.matchtype, matching.matchfiles["A"], matching.matchfiles["B"]) # report.setMatchResult("TC0001", "comp01", "arte01", m, "result" + str(i), "" + str(i) + "
") htmltext = report.getTitle(getattr(job.par, "testcase"), matching.comp.name, table, matching.matchtype) htmltext += report.getComponentHead(getattr(job.par, "testcase"), matching.comp.name, table, matching.matchtype) htmltext += report.getArtefactBlock(getattr(job.par, "testcase"), matching.comp.name, table, matching.matchtype) htmltext += compareRows(matching, path) matching.htmltext += htmltext def compareRows(matching, path): """ traverse through matched rows """ job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 1 ddl = matching.getTableDdl(path) report = utils.report_tool.Report.getInstance() table = "" header = [] # "

Tabelle : "+table+"

" htmltext = "" for f in ddl[B.DATA_NODE_HEADER]: job.debug(verify, "ddl " + f + " ") header.append({"field": f, "type": ddl[f]["type"], "acceptance": ddl[f]["acceptance"]}) htmltext += "" htmltext += "" matching.difftext = htmltext for k in sorted(matching.linksA): print(k) if (matching.isHitA(k)): htmltext += compareRow(matching, header, matching.sideA[int(extractKeyI(k))], matching.sideB[int(extractKeyI(matching.linksA[k]))]) else: htmltext += markRow(matching, header, matching.sideA[int(extractKeyI(k))], "A") matching.setCssClass("result3") for k in sorted(matching.linksB): if (not matching.isHitB(k)): htmltext += markRow(matching, header, matching.sideB[int(extractKeyI(k))], "B") matching.setCssClass("result2") htmltext += "
" + f + "
" matching.difftext += "" return htmltext def markRow(matching, header, row, side): job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 4 text = "" cssClass = "" for f in header: if not f["field"] in row: val = "" cssClass = "novalue" elif side == "A": res = getEvaluation(matching, f["type"], f["acceptance"], row[f["field"]], "") val = str(row[f["field"]]) cssClass = res[1] else: res = getEvaluation(matching, f["type"], f["acceptance"], "", row[f["field"]]) val = str(row[f["field"]]) cssClass = res[2] text += "" + val + "" text = "" \ + M.MATCH[M.MATCH[matching.matchtype][side]]["short"] + "" + text + "" matching.difftext += text return text def compareRow(matching, header, rA, rB): """ traverse through matched rows """ job = basic.program.Job.getInstance() verify = int(job.getDebugLevel("match_tool")) - 4 allident = True textA = "" textB = "" text = "" valA = "" valB = "" classA = "novalue" classB = "novalue" for f in header: print(f) if f["field"] in rA and f["field"] in rB: res = getEvaluation(matching, f["type"], f["acceptance"], rA[f["field"]], rB[f["field"]]) match = res[0] classA = res[1] classB = res[2] valA = str(rA[f["field"]]) valB = str(rB[f["field"]]) elif f["field"] in rA: valA = str(rA[f["field"]]) match = "ddl" classA = "acceptA" classB = "acceptB" elif f["field"] in rB: valB = str(rB[f["field"]]) match = "ddl" classA = "acceptA" classB = "acceptB" else: match = "MATCH" classA = "acceptA" classB = "acceptB" if (match == "MATCH"): textA += "" + valA + "" textB += "" + valB + "" matching.setCssClass("result1") elif (match == "hard"): allident = False textA += "" + valA + "" textB += "" + valB + "" matching.setCssClass("result3") else: allident = False textA += "" + valA + " (" + match + ")" textB += "" + valB + " (" + match + ")" matching.setCssClass("result1") if allident: return "" + textA + "" text = "" + M.MATCH[M.MATCH[matching.matchtype]["A"]]["short"] + "" + textA + "" text += "" + M.MATCH[matching.matchtype]["shortB"] + "" + textB + "" matching.difftext += text return text # -------------------------------------------------------------------------- def matchLines(matching): pass