From c2e1ff6c8ec606422c6287d1a1abbff5d3b04a40 Mon Sep 17 00:00:00 2001 From: Adrien Bourmault Date: Thu, 1 Aug 2019 16:52:36 +0200 Subject: [PATCH] Finished the analyzer --- src/globs.py | 2 +- src/ihm.py | 3 +- src/main.py | 132 ++++++++++++++++++++++++++++++++++++++------------- src/mrz.py | 99 +++++++++++++++++++++++++++++++++++--- 4 files changed, 194 insertions(+), 42 deletions(-) diff --git a/src/globs.py b/src/globs.py index eebde99..f35f0dd 100644 --- a/src/globs.py +++ b/src/globs.py @@ -37,7 +37,7 @@ CNIRFolder = os.getcwd() CNIRLColor = "#006699" CNIRName = "CNIRevelator {}".format(verstring) CNIRCryptoKey = '82Xh!efX3#@P~2eG' -CNIRNewVersion = True +CNIRNewVersion = False CNIRConfig = CNIRFolder + '\\config\\conf.ig' CNIRErrLog = CNIRFolder + '\\logs\\error.log' diff --git a/src/ihm.py b/src/ihm.py index 3b2150c..2693357 100644 --- a/src/ihm.py +++ b/src/ihm.py @@ -32,7 +32,7 @@ import logger # logger.py import globs # globs.py -controlKeys = ["Right", "Left", "Up", "Down", "Home", "End", "Delete", "BackSpace", "Inser", "Shift_L", "Shift_R", "Control_R", "Control_L"] +controlKeys = ["Right", "Left", "Up", "Down", "Home", "End", "Delete", "Inser", "Shift_L", "Shift_R", "Control_R", "Control_L"] class DocumentAsk(Toplevel): @@ -96,6 +96,7 @@ class LoginDialog(Toplevel): x = ws / 2 - w / 2 y = hs / 2 - h / 2 self.geometry('%dx%d+%d+%d' % (w, h, x, y)) + self.bind("", self.connecti) def connecti(self): self.login = self.entry_login.get().strip() diff --git a/src/main.py b/src/main.py index 3ad3bbf..8d2b565 100644 --- a/src/main.py +++ b/src/main.py @@ -53,6 +53,7 @@ class mainWindow(Tk): self.mrzChar = '' self.mrzDecided = False self.Tags = [] + self.compliance = True # Get the screen size ws = self.winfo_screenwidth() @@ -125,6 +126,21 @@ class mainWindow(Tk): self.pays['text'] = 'Inconnu(e)' self.indic['text'] = 'Inconnu(e)' + + self.infoList = \ + { + "NOM" : self.nom, + "PRENOM" : self.prenom, + "BDATE" : self.bdate, + "DDATE" : self.ddate, + "EDATE" : self.edate, + "NO" : self.no, + "SEX" : self.sex, + "NAT" : self.nat, + "PAYS" : self.pays, + "INDIC" : self.indic, + } + # The STATUS indicator self.STATUT = ttk.Labelframe(self, text='Statut') self.STATUT.grid_columnconfigure(0, weight=1) @@ -224,11 +240,11 @@ class mainWindow(Tk): # Some bindings self.termtext.bind('', self.entryValidation) self.termtext.bind('<>', self.pasteValidation) - self.speed731text.bind('', self.speedValidation) + self.speed731text.bind('', self.speedValidation) self.update() logfile.printdbg('Initialization successful') - def stringValidation(self): + def stringValidation(self, char): # analysis # If we must decide the type of the document if not self.mrzDecided: @@ -244,33 +260,30 @@ class mainWindow(Tk): self.wait_window(invite) - self.logOnTerm("Document detecté : {}".format(candidates[invite.choice][2])) + self.logOnTerm("Document detecté : {}\n".format(candidates[invite.choice][2])) self.mrzDecided = candidates[invite.choice] elif len(candidates) == 1: - self.logOnTerm("Document detecté : {}".format(candidates[0][2])) + self.logOnTerm("Document detecté : {}\n".format(candidates[0][2])) self.mrzDecided = candidates[0] else: # break the line if (len(self.mrzChar) - 2 >= len(self.mrzDecided[0][0])) and ("\n" not in self.mrzChar[:-1]): # In case of there is no second line if len(self.mrzDecided[0][1]) == 0: - self.mrzChar = self.termtext.get("1.0", "end")[:-2] + self.mrzChar = self.termtext.get("1.0", "end")[:-1] self.termtext.delete("1.0","end") - self.termtext.insert("1.0", self.mrzChar) - self.nope = True + self.termtext.insert("1.0", self.mrzChar[:-1]) else: # In case of there is a second line self.mrzChar = self.termtext.get("1.0", "end")[:-1] + '\n' self.termtext.delete("1.0","end") self.termtext.insert("1.0", self.mrzChar) - self.nope = True # stop when limit reached elif (len(self.mrzChar) - 3 >= 2 * len(self.mrzDecided[0][0])): - self.mrzChar = self.termtext.get("1.0", "end")[:-2] + self.mrzChar = self.termtext.get("1.0", "end")[:-1] self.termtext.delete("1.0","end") - self.termtext.insert("1.0", self.mrzChar) - + self.termtext.insert("1.0", self.mrzChar[:-1]) # compute the control sum if needed self.computeSigma() @@ -278,10 +291,16 @@ class mainWindow(Tk): """ On the fly validation with regex """ - print("go") - controlled = False + # get the cursor + if self.mrzDecided: + position = self.termtext.index(INSERT).split(".") + pos = (int(position[0]) - 1) * len(self.mrzDecided[0][0]) + (int(position[1]) - 1) + else: + position = self.termtext.index(INSERT).split(".") + pos = (int(position[1]) - 1) + # verifying that there is no Ctrl-C/Ctrl-V and others if event.state & 0x0004 and ( event.keysym == "c" or event.keysym == "v" or @@ -298,9 +317,6 @@ class mainWindow(Tk): regex = re.compile("[^A-Z0-9<]") code = re.sub(regex, '', self.mrzChar) - position = self.termtext.index(INSERT).split(".") - pos = (int(position[0]) - 1) * len(self.mrzDecided[0][0]) + (int(position[1]) - 1) - number = mrz.completeDocField(self.mrzDecided, code, pos) - 1 if number == 0: @@ -321,11 +337,12 @@ class mainWindow(Tk): if not regex.fullmatch(event.char): self.logOnTerm("Caractère non accepté !\n") return "break" - # Adds the entry - self.mrzChar = self.termtext.get("1.0", "end")[:-1] + event.char + '\n' + # Adds the entry + tempChar = self.termtext.get("1.0", "end")[:-1] + self.mrzChar = tempChar[:pos+1] + event.char + tempChar[pos+1:] + '\n' # validation of the mrz string - self.stringValidation() + self.stringValidation(event.char) def pasteValidation(self, event): """ @@ -347,7 +364,7 @@ class mainWindow(Tk): for char in lines: self.termtext.insert("1.0", self.mrzChar) self.mrzChar = self.mrzChar + char - self.stringValidation() + self.stringValidation("") return "break" @@ -365,6 +382,12 @@ class mainWindow(Tk): self.monlog['state'] = 'disabled' self.monlog.yview(END) + def clearTerm(self): + self.monlog['state'] = 'normal' + self.monlog.delete('1.0', 'end') + self.monlog['state'] = 'disabled' + self.monlog.yview(END) + def speedResultPrint(self, text): self.speedResult['state'] = 'normal' self.speedResult.delete("1.0", 'end') @@ -417,24 +440,67 @@ class mainWindow(Tk): parent=self) - # XXX def computeSigma(self): + """ + Launch the checksum computation, infos validation and display the results + """ # the regex regex = re.compile("[^A-Z0-9<]") code = re.sub(regex, '', self.mrzChar) + self.compliance = True - allSums = mrz.computeAllControlSum(self.mrzDecided, code) - print("Code : {} | Sums : {}".format(code, allSums)) - - - - - - - - - - + allSums = mrz.computeAllControlSum(self.mrzDecided, code)["ctrlSumList"] + #print("Code : _{}_ | Sums : {}".format(code, allSums)) + + self.termtext.tag_remove("conforme", "1.0", "end") + self.termtext.tag_remove("nonconforme", "1.0", "end") + + self.clearTerm() + self.logOnTerm("Examen du document : {}\n\n".format(self.mrzDecided[2])) + + for sum in allSums: + x = sum[1] // len(self.mrzDecided[0][0]) +1 + y = sum[1] % len(self.mrzDecided[0][0]) + #print("index : {}.{}".format(x,y)) + #print("{} == {}".format(code[sum[1]], sum[2])) + + self.logOnTerm("Somme de contrôle position {} : Lu {} VS Calculé {}\n".format(sum[1], code[sum[1]], sum[2])) + + # if sum is facultative or if sum is ok + if sum[3] or int(code[sum[1]]) == int(sum[2]): + self.termtext.tag_add("conforme", "{}.{}".format(x,y), "{}.{}".format(x,y+1)) + self.termtext.tag_configure("conforme", background="green", foreground="white") + else: + self.termtext.tag_add("nonconforme", "{}.{}".format(x,y), "{}.{}".format(x,y+1)) + self.termtext.tag_configure("nonconforme", background="red", relief='raised', foreground="white") + self.compliance = False + + + if self.compliance == True: + self.STATUStxt['text'] = 'CONFORME' + self.STATUStxt['fg'] = "green" + else: + self.STATUStxt['text'] = 'NON CONFORME' + self.STATUStxt['fg'] = "red" + + # get the infos + docInfos = mrz.getDocInfos(self.mrzDecided, code) + #print(docInfos) + # display the infos + for key in [ e for e in docInfos ]: + print(docInfos[key]) + if key in ["CODE", "CTRL"]: + continue + if not docInfos[key] == False: + self.infoList[key]['text'] = docInfos[key] + self.infoList[key]['background'] = self['background'] + self.infoList[key]['foreground'] = "black" + else: + self.infoList[key]['background'] = "red" + self.infoList[key]['foreground'] = "white" + self.infoList[key]['text'] = "NC" + + return diff --git a/src/mrz.py b/src/mrz.py index cd1ff05..bbb80bb 100644 --- a/src/mrz.py +++ b/src/mrz.py @@ -26,6 +26,8 @@ import re import logger # logger.py +import re +import datetime ## SEX CODES sexcode = {'M':'Homme', 'F':'Femme', 'X':'Non spécifié'} @@ -717,13 +719,12 @@ I__ = [ ] IDFR = [ - ["11222333333333333333333333333344444E", "555566677777899999999999999AAAAAABCD"], + ["112223333333333333333333333333444444", "555566677777899999999999999AAAAAABCD"], { "1": ["2", "CODE", "ID"], "2": ["3", "PAYS", "FRA"], "3": ["25", "NOM", "([A-Z]|<)+"], "4": ["6", "NOINT", ".+"], - "E": ["1", "CTRL", "[0-9]", "1234"], "5": ["4", "DDATE", "[0-9]+"], "6": ["3", "NOINT2", "[0-9]+"], "7": ["5", "NOINT3", "[0-9]+"], @@ -901,8 +902,10 @@ def computeControlSum(code): def computeAllControlSum(doc, code): """ This function computes all the ctrl sums on a MRZ string and returns all the results + it returns the misc infos about the document too """ ctrlSumList = [] + facult = False # iteration on each char of the given MRZ for charPos in range(len(code)): @@ -924,12 +927,94 @@ def computeAllControlSum(doc, code): ctrlSum = computeControlSum(codeChain) #print("SUM : {} vs {}".format(code[charPos], ctrlSum)) - ctrlSumList += [ (field, charPos, ctrlSum) ] - - return ctrlSumList - - + ctrlSumList += [ (field, charPos, ctrlSum, facult) ] + if doc[1][field][1] == "CTRLF": + #print("{} is CTRL field {}".format(code[charPos], field)) + + codeChain = "" + # iteration on the fields to control + for pos in range(len(code)): + target = getDocString(doc)[pos] + if target in doc[1][field][3]: + #print("__field : {} {} {} {}".format(target, pos, field, doc[1][field][3])) + codeChain += code[pos] + + #print("chain to control : _{}_".format(codeChain)) + + ctrlSum = computeControlSum(codeChain) + #print("SUM : {} vs {}".format(code[charPos], ctrlSum)) + + if code[charPos] == "<": + facult = True + + ctrlSumList += [ (field, charPos, ctrlSum, facult) ] + + return { + "ctrlSumList" : ctrlSumList + } + + +def getDocInfos(doc, code): + # get all the types of infos that are in the document doc + infoTypes = [ (doc[1][field][1], limits(doc[0][0] + doc[0][1], field)) for field in doc[1] ] + + res = {} + + for field in infoTypes: + + value = code[ field[1][0] : field[1][1] ] + + # State code + if field[0] == 'PAYS' or field[0] == 'NAT': + try: + if len(value) == 3 and value[-1] != "<": + res[field[0]] = landcode3[value] + elif len(value) == 3 and value[-1] == "<": + res[field[0]] = landcode2[value[:-1]] + else: + res[field[0]] = landcode2[value] + except KeyError: + res[field[0]] = False + + # Dates + elif field[0][1:] == 'DATE': + # size adaptation + if len(value) == 6: + value = "{}/{}/{}".format(value[4:6], value[2:4], value[0:2]) + elif len(value) == 4: + value = "{}/{}/{}".format("01", value[2:4], value[0:2]) + + # date validation + try: + datetime.datetime.strptime(value,"%d/%m/%y") + except ValueError: + print(value) + if value != "": + res[field[0]] = False + else: + res[field[0]] = value + + # Numbers + elif field[0][:-1] == 'NOINT': + try: + res["NO"] += value + except KeyError: + res["NO"] = value + elif field[0] == 'NOINT': + try: + res["NO"] += value + except KeyError: + res["NO"] = value + + elif field[0] == 'FACULT': + res["INDIC"] += value + # All other cases + else: + if value != "": + res[field[0]] = value.replace("<", " ").strip() + + return res