From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: mx.groups.io; dkim=missing; spf=pass (domain: intel.com, ip: 134.134.136.24, mailfrom: zhijux.fan@intel.com) Received: from mga09.intel.com (mga09.intel.com [134.134.136.24]) by groups.io with SMTP; Tue, 25 Jun 2019 22:21:36 -0700 X-Amp-Result: UNSCANNABLE X-Amp-File-Uploaded: False Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by orsmga102.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 25 Jun 2019 22:21:35 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.63,418,1557212400"; d="dat'59?scan'59,208,59";a="188534132" Received: from fmsmsx105.amr.corp.intel.com ([10.18.124.203]) by fmsmga002.fm.intel.com with ESMTP; 25 Jun 2019 22:21:35 -0700 Received: from fmsmsx156.amr.corp.intel.com (10.18.116.74) by FMSMSX105.amr.corp.intel.com (10.18.124.203) with Microsoft SMTP Server (TLS) id 14.3.439.0; Tue, 25 Jun 2019 22:21:34 -0700 Received: from shsmsx152.ccr.corp.intel.com (10.239.6.52) by fmsmsx156.amr.corp.intel.com (10.18.116.74) with Microsoft SMTP Server (TLS) id 14.3.439.0; Tue, 25 Jun 2019 22:21:34 -0700 Received: from shsmsx101.ccr.corp.intel.com ([169.254.1.87]) by SHSMSX152.ccr.corp.intel.com ([169.254.6.225]) with mapi id 14.03.0439.000; Wed, 26 Jun 2019 13:21:33 +0800 From: "Fan, ZhijuX" To: "devel@edk2.groups.io" CC: "Gao, Liming" , "Feng, Bob C" , Ard Biesheuvel , "Leif Lindholm" , "Kinney, Michael D" Subject: [edk2-platform patch V4] Platform/Intel:Add UniTool into edk2-platforms/Platform/Intel/Tools Thread-Topic: [edk2-platform patch V4] Platform/Intel:Add UniTool into edk2-platforms/Platform/Intel/Tools Thread-Index: AdUr3wOYPeDSy9PbQpOHVGooTPJOhw== Date: Wed, 26 Jun 2019 05:21:33 +0000 Message-ID: Accept-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: dlp-product: dlpe-windows dlp-version: 11.0.600.7 dlp-reaction: no-action x-originating-ip: [10.239.127.40] MIME-Version: 1.0 Return-Path: zhijux.fan@intel.com X-Groupsio-MsgNum: 42865 Content-Type: multipart/mixed; boundary="_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF84C58SHSMSX101ccrcor_" Content-Language: en-US --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF84C58SHSMSX101ccrcor_ Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable BZ:https://bugzilla.tianocore.org/show_bug.cgi?id=3D1855 UniTool is one python script to generate UQI (Universal Question Identifier) unicode string for HII question PROMPT string. UQI string can be used to identify each HII question. The scripts function will sync up UQI definitions with uni files based on vfi/vfr/hfr/sd/sdi in the tree. This script can be run in both Py2 and Py3. Cc: Liming Gao Cc: Bob Feng Cc: Ard Biesheuvel Cc: Leif Lindholm Cc: Michael D Kinney Signed-off-by: Zhiju.Fan --- Updata help message Platform/Intel/Tools/UniTool/README.txt | 41 +++ Platform/Intel/Tools/UniTool/UniTool.py | 487 ++++++++++++++++++++++++++++= ++++ 2 files changed, 528 insertions(+) create mode 100644 Platform/Intel/Tools/UniTool/README.txt create mode 100644 Platform/Intel/Tools/UniTool/UniTool.py diff --git a/Platform/Intel/Tools/UniTool/README.txt b/Platform/Intel/Tools= /UniTool/README.txt new file mode 100644 index 0000000000..69da4aca24 --- /dev/null +++ b/Platform/Intel/Tools/UniTool/README.txt @@ -0,0 +1,41 @@ + +How to use UniTool +----------------- +The usage of the tool is: +UniTool.py [-b] [-u] [-l] [-x] [-h] [-d 'rootDirectory1'] [-d 'rootDirecto= ry2'] [-d 'rootDirectory3']... [-q e|w] + 'rootDirectory0' 'uqiFile'|'uqiFileDirectory' ['excludedDirecto= ry1'] ['excludedDirectory2'] ['excludedDirectory3']... + +Function will sync up UQI definitions with uni files based on vfi/vfr/hfr/= sd/sdi in the tree. + +Required Arguments: + 'rootdirectory0' path to root directory + 'uqiFileDirectory' path to UQI file(UqiList.uni) + 'uqiFile' UQI file + +Options: + -h Show this help + -b Build option returns error if any new UQI needs as= signing + based on vfi/vfr/hfr/sd/sdi when no -u option is s= pecified + -u Create new UQIs that does not already exist in uqi= File for + any string requiring a UQI based on vfi/vfr/hfr/sd= /sdi + NOTE: 'uqiFile' cannot be readonly! + -l Language deletion option (keeps only English and u= qi) + moves all UQIs to 'uqiFile' + NOTE: Uni files cannot be readonly! + -x Exclude 'rootDirectory'/'excludedDirectory1' & + 'rootDirectory'/'excludedDirectory2'... from UQI l= ist build + NOTE: Cannot be the same as rootDirectory + -d Add multiple root directories to process + -q Print warning(w) or return error(e) if different H= II questions + are referring same string token + +Return error if any duplicated UQI string or value in UQI list or if no de= finition +for any string referred by HII question when -b or -u is specified + +NOTE: Options must be specified before parameters + +Notice +----------------- +- "S0000" style will be used if uqiFile needs to be new created. + Use the same uqi style if uqiFile is existed. For example, + if the exist UqiFile use "\x0000" style, "\x0000" will be used. diff --git a/Platform/Intel/Tools/UniTool/UniTool.py b/Platform/Intel/Tools= /UniTool/UniTool.py new file mode 100644 index 0000000000..f5cffaf8bc --- /dev/null +++ b/Platform/Intel/Tools/UniTool/UniTool.py @@ -0,0 +1,487 @@ +## @file +# generate UQI (Universal Question Identifier) unicode string for HII ques= tion PROMPT string. UQI string can be used to +# identify each HII question. +# +# Copyright (c) 2019, Intel Corporation. All rights reserved. +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# + +import re +import sys +import os +import getopt +import codecs +import fnmatch +import logging +import argparse + +# global variable declarations +QuestionError =3D False +UqiList =3D re.compile('^#string[ \t]+([A-Z_0-9]+)[ \t]+#language[ \t]+uqi= [ \t\r\n]+"(?:[x\S]{1,2})([0-9a-fA-F]{4,5})"', + re.M).findall +AllUqis =3D {} +StringDict =3D {} +GlobalVarId =3D {} +Options =3D {} + +# Version message +__prog__ =3D 'UniTool' +__description__ =3D 'The script generate UQI unicode string for HII questi= on PROMPT string.\n' +__copyright__ =3D 'Copyright (c) 2019, Intel Corporation. All rights reser= ved.
' +__version__ =3D '%s Version %s' % (__prog__, '0.1 ') +_Usage =3D "Syntax: %s [-b] [-u] [-l] [-x] [-h] [-d 'rootDirectory1'] [-d= 'rootDirectory2'] [-d 'rootDirectory3']... \n[-q e|w]" \ + "'rootDirectory0' 'uqiFile'|'uqiFileDirectory' ['excludedDirector= y1'] ['excludedDirectory2'] ['excludedDirectory3']...\n" \ + % (os.path.basename(sys.argv[0])) + +# ********************************************************************** +# description: Get uni file encoding +# +# arguments: Filename - name of uni file +# +# returns: utf-8 or utf-16 +# +def GetUniFileEncoding(Filename): + # + # Detect Byte Order Mark at beginning of file. Default to UTF-8 + # + Encoding =3D 'utf-8' + + # + # Read file + # + try: + with open(Filename, mode=3D'rb') as UniFile: + FileIn =3D UniFile.read() + except: + return Encoding + + if (FileIn.startswith(codecs.BOM_UTF16_BE) or FileIn.startswith(codecs= .BOM_UTF16_LE)): + Encoding =3D 'utf-16' + + return Encoding + + +# rewrite function os.path.walk +def Walk(Top, Func, Arg): + try: + Names =3D os.listdir(Top) + except os.error: + return + Func(Arg, Top, Names) + for Name in Names: + Name =3D os.path.join(Top, Name) + if os.path.isdir(Name): + Walk(Name, Func, Arg) + + +# ********************************************************************** +# description: Parses commandline arguments and options +# Calls function processUni to build dictionary of strings +# Calls other functions according to user specified options +# +# arguments: argv - contains all input from command line +# - must contain path to root directory +# - may contain options -h, -u, -l, -b or -x before path +# +# returns: none +# +def main(): + ##### Read input arguments and options + global AllUqis, UqiList, QuestionError + parser =3D argparse.ArgumentParser(prog=3D__prog__, + description=3D__description__ + __cop= yright__, + usage=3D_Usage, + conflict_handler=3D'resolve') + parser.add_argument('Path', nargs=3D'+', + help=3D'the path for files to be converted.It coul= d be directory or file path.') + parser.add_argument('-v', '--version', action=3D'version', version=3D_= _version__, + help=3D"show program's version number and exit") + parser.add_argument('-b', '--build', action=3D'store_true', dest=3D'Bu= ildOption', + help=3D"Build option returns error if any new UQI = needs assigning " \ + "based on vfi/vfr/hfr/sd/sdi when no -u optio= n is specified") + parser.add_argument('-u', '--updata', action=3D'store_true', dest=3D'U= pdateUQIs', + help=3D"Create new UQIs that does not already exis= t in uqiFile for" \ + "any string requiring a UQI based on vfi/vfr/= hfr/sd/sdi" \ + "NOTE: 'uqiFile' cannot be readonly!") + parser.add_argument('-l', '--lang', action=3D'store_true', dest=3D'Lan= gOption', + help=3D"Language deletion option (keeps only Engli= sh and uqi)" \ + "moves all UQIs to 'uqiFile', NOTE: Uni files= cannot be readonly!") + parser.add_argument('-x', '--exclude', action=3D'store_true', dest=3D'= ExcludeOption', + help=3D"Exclude 'rootDirectory'/'excludedDirectory= 1' &" \ + "'rootDirectory'/'excludedDirectory2'... from= UQI list build") + parser.add_argument('-d', '--dir', action=3D'append', metavar=3D'FILED= IR', dest=3D'DirName', + help=3D"Add multiple root directories to process") + parser.add_argument('-q', '--question', dest=3D'Question', choices=3D[= 'w', 'e'], + help=3D"Print warning(w) or return error(e) if dif= ferent HII questions" \ + "are referring same string token") + Opts =3D parser.parse_args() + Destname =3D '' + DirNameList =3D [] + ExDirList =3D [] + if Opts.Path: + DirNameList.append(Opts.Path[0]) + Destname =3D Opts.Path[1] + ExDirList =3D Opts.Path[2:] + if Opts.DirName: + DirNameList.extend(Opts.DirName) + QuestionOption =3D Opts.Question + ExcludeOption =3D Opts.ExcludeOption + BuildOption =3D Opts.BuildOption + UpdateUQIs =3D Opts.UpdateUQIs + LangOption =3D Opts.LangOption + ExPathList =3D [] + + if ExDirList: + try: + for EachExDir in ExDirList: + for EachRootDir in DirNameList: + if EachExDir =3D=3D EachRootDir: + print("\nERROR: excludedDirectory is same as rootD= irectory\n") + return + ExPathList.append(EachRootDir + os.sep + EachExDir) + except: + print(_Usage) + return + + global Options + Options =3D {'Destname': Destname, 'DirNameList': DirNameList, 'ExPath= List': ExPathList, 'BuildOption': BuildOption, + 'UpdateUQIs': UpdateUQIs, 'LangOption': LangOption, 'Exclud= eOption': ExcludeOption, + 'QuestionOption': QuestionOption} + print("UQI file: %s" % Destname) + for EachDirName in DirNameList: + Walk(EachDirName, processUni, None) + if QuestionError: + return + if os.path.isdir(Options['Destname']): + DestFileName =3D Options['Destname'] + os.sep + 'UqiList.uni' + else: + DestFileName =3D Options['Destname'] + if os.path.exists(DestFileName) and (DestFileName not in list(AllUqis.= keys())): + try: + Encoding =3D GetUniFileEncoding(DestFileName) + with codecs.open(DestFileName, 'r+', Encoding) as destFile: + DestFileBuffer =3D destFile.read() + except IOError as e: + print("ERROR: " + e.args[1]) + return + AllUqis[DestFileName] =3D UqiList(DestFileBuffer) + if BuildOption: + ReturnVal =3D newUqi() + if (ReturnVal =3D=3D 1): + print('Please fix UQI ERROR(s) above before proceeding.') + else: + print("No UQI issues detected\n") + return + + +# ********************************************************************** +# description: newUqi collects a list of all currently used uqi values in = the tree +# Halt build if any duplicated string or value in UQI list. +# If -u option was specified, creates new UQIs that does not +# already exist in uqiFile for any string requiring a UQI. +# +# arguments: none +# +# returns: 0 on success +# 1 on error - this should cause the build to halt +# + +Syntax =3D "S" +SyntaxRE =3D re.compile('#string[ \t]+[A-Z_0-9]+[ \t]+#language[ \t]+uqi[ = \t\r\n]+"([x\S]{1,2}).*', re.DOTALL).findall + + +def newUqi(): + global Options, GlobalVarId, AllUqis, Syntax, SyntaxRE + UqiRange =3D [] + UqiStringList =3D [] + CreateUQI =3D [] + ReturnVal =3D 0 + BaseNumSpaces =3D 47 # Used to line up the UQI values in the resultin= g uqiFile + + # Look for duplication in the current UQIs and collect current range o= f UQIs + for path in AllUqis.keys(): + for UqiString in AllUqis[path]: # path contains the path and File= name of each uni file + # Checks for duplicated strings in UQI list + for TempString in UqiStringList: + if TempString =3D=3D UqiString[0]: + print("ERROR: UQI string %s was assigned more than onc= e and will cause corruption!" % UqiString[0]) + print("Delete one occurrence of the string and rerun t= ool.") + ReturnVal =3D 1 # halt build + + UqiStringList.append(UqiString[0]) + + # Checks for duplicated UQI values in UQI list + if int(UqiString[1], 16) in UqiRange: + print("ERROR: UQI value %04x was assigned more than once a= nd will cause corruption!" % int(UqiString[1], + = 16)) + print("Delete one occurrance of the UQI and rerun tool to = create alternate value.") + ReturnVal =3D 1 # halt build + UqiRange.append(int(UqiString[1], 16)) + + for StringValue in GlobalVarId.keys(): + StringFound =3D False + for path in StringDict.keys(): + for UniString in StringDict[path]: # path contains the path a= nd Filename of each uni file + if (StringValue =3D=3D UniString): + StringFound =3D True + break + if not StringFound: + print("ERROR: No definition for %s referred by HII question" %= (StringValue)) + ReturnVal =3D 1 # halt build + + # Require a UQI for any string in vfr/vfi files + for StringValue in GlobalVarId.keys(): + # Ignore strings defined as STRING_TOKEN(0) + if (StringValue !=3D "0"): + # Check if this string already exists in the UQI list + if (StringValue not in UqiStringList) and (StringValue not in = CreateUQI): + CreateUQI.append(StringValue) + if not Options['UpdateUQIs']: + print("ERROR: No UQI for %s referred by HII question" = % (StringValue)) + ReturnVal =3D 1 # halt build after printing all error= messages + + if (ReturnVal =3D=3D 1): + return ReturnVal + + # Update uqiFile with necessary UQIs + if Options['UpdateUQIs'] and CreateUQI: + if os.path.isdir(Options['Destname']): + DestFileName =3D Options['Destname'] + os.sep + 'UqiList.uni' + else: + DestFileName =3D Options['Destname'] + try: + Encoding =3D GetUniFileEncoding(DestFileName) + with codecs.open(DestFileName, 'r+', Encoding) as OutputFile: + PlatformUQI =3D OutputFile.read() + except IOError as e: + print("ERROR: " + e.args[1]) + if (e.args[0] =3D=3D 2): + try: + with codecs.open(DestFileName, 'w', Encoding) as Outpu= tFile: + print(DestFileName + " did not exist. Creating ne= w file.") + PlatformUQI =3D '' + except: + print("Error creating " + DestFileName + ".") + return 1 + if (e.args[1] =3D=3D "Permission denied"): + print( + "\n%s is Readonly. You must uncheck the ReadOnly atti= bute to run the -u option.\n" % DestFileName) + return 1 + + # Determines and sets the UQI number format + # TODO: there is probably a more elegant way to do this... + SyntaxL =3D SyntaxRE(PlatformUQI) + if len(SyntaxL) !=3D 0: + Syntax =3D SyntaxL[0] + + # script is reading the file in and writing it back instead of app= ending because the codecs module + # automatically adds a BOM wherever you start writing. This caused= build failure. + UqiRange.sort() + if (UqiRange =3D=3D []): + NextUqi =3D 0 + else: + NextUqi =3D UqiRange[len(UqiRange) - 1] + 1 + + for StringValue in CreateUQI: + print("%s will be assigned a new UQI value" % StringValue) + UqiRange.append(NextUqi) + # + # Lines up the UQI values in the resulting uqiFile + # + Spaces =3D " " * (BaseNumSpaces - len(StringValue)) + PlatformUQI +=3D '#string %s%s #language uqi \"%s%04x\"\r\n' %= (StringValue, Spaces, Syntax, NextUqi) + print("#string %s%s #language uqi \"%s%04X\"" % (StringValue,= Spaces, Syntax, NextUqi)) + NextUqi +=3D 1 + + with codecs.open(DestFileName, 'r+', Encoding) as OutputFile: + OutputFile.seek(0) + OutputFile.write(PlatformUQI) + + return 0 + + +# ********************************************************************** +# description: Parses each uni file to collect dictionary of strings +# Removes additional languages and overwrites current uni fil= es +# if -l option was specified +# +# arguments: path - directory location of file including file name +# Filename - name of file to be modified +# +# returns: error string if failure occurred; +# none if completed sucessfully +# +# the following are global so that parsefile is quicker + +FindUniString =3D re.compile( + '^#string[ \t]+([A-Z_0-9]+)(?:[ \t\r\n]+#language[ \t]+[a-zA-Z-]{2,5}[= \t\r\n]+".*"[ \t]*[\r]?[\n]?)*', + re.M).findall + +OtherLang =3D re.compile( + '^#string[ \t]+[A-Z_0-9]+(?:[ \t\r\n]+#language[ \t]+[a-zA-Z-]{2,5}[ \= t\r\n]+".*"[ \t]*[\r]?[\n]?)*', re.M).findall +EachLang =3D re.compile('[ \t\r\n]+#language[ \t]+([a-zA-Z-]{2,5})[ \t\r\n= ]+".*"[ \t]*[\r]?[\n]?').findall + +UqiStrings =3D re.compile('^#string[ \t]+[A-Z_0-9]+[ \t]+#language[ \t]+uq= i[ \t\r\n]+".*"[ \t]*[\r]?[\n]?', re.M) + + +def parsefile(path, Filename): + global Options, StringDict, AllUqis, UqiList, FindUniString, OtherLang= , EachLang, UqiStrings + + FullPath =3D path + os.sep + Filename + + try: + UniEncoding =3D GetUniFileEncoding(FullPath) + with codecs.open(FullPath, 'r', UniEncoding) as UniFile: + Databuffer =3D UniFile.read() + except: + print("Error opening " + FullPath + " for reading.") + return + WriteFile =3D False + + if os.path.isdir(Options['Destname']): + DestFileName =3D Options['Destname'] + os.sep + 'UqiList.uni' + else: + DestFileName =3D Options['Destname'] + + if Options['LangOption']: + try: + UqiEncoding =3D GetUniFileEncoding(DestFileName) + with codecs.open(DestFileName, 'r+', UqiEncoding) as OutputFil= e: + PlatformUQI =3D OutputFile.read() + except IOError as e: + print("ERROR: " + e.args[1]) + if (e.args[0] =3D=3D 2): + try: + with codecs.open(DestFileName, 'w', UqiEncoding) as Ou= tputFile: + print(DestFileName + " did not exist. Creating ne= w file.") + PlatformUQI =3D '' + except: + print("Error creating " + DestFileName + ".") + return + else: + print("Error opening " + DestFileName + " for appending.") + return + + if (Filename !=3D DestFileName.split(os.sep)[-1]): + Uqis =3D re.findall(UqiStrings, Databuffer) + if Uqis: + for Uqi in Uqis: + PlatformUQI +=3D Uqi + with codecs.open(DestFileName, 'r+', UqiEncoding) as Outpu= tFile: + OutputFile.seek(0) + OutputFile.write(PlatformUQI) + Databuffer =3D re.sub(UqiStrings, '', Databuffer) + if Uqis: + WriteFile =3D True + print("Deleted uqis from %s" % FullPath) + stringlist =3D OtherLang(Databuffer) + for stringfound in stringlist: + ThisString =3D EachLang(stringfound) + for LanguageFound in ThisString: + if ((LanguageFound !=3D 'en') and (LanguageFound !=3D = 'en-US') and (LanguageFound !=3D 'eng') and ( + LanguageFound !=3D 'uqi')): + Databuffer =3D re.sub(re.escape(stringfound), '', = Databuffer) + WriteFile =3D True + print("Deleted %s from %s" % (LanguageFound, FullP= ath)) + if (Filename !=3D DestFileName.split(os.sep)[-1]): + # adding strings to dictionary + StringDict[r'%s' % FullPath] =3D FindUniString(Databuffer) + # adding UQIs to dictionary + AllUqis[r'%s' % FullPath] =3D UqiList(Databuffer) + + if WriteFile: + try: + with codecs.open(FullPath, 'w', UniEncoding) as UniFile: + UniFile.write(Databuffer) + except: + print("Error opening " + FullPath + " for writing.") + return + + +# ********************************************************************** +# description: Searches tree for uni files +# Calls parsefile to collect dictionary of strings in each un= i file +# Calls searchVfiFile for each vfi or vfr file found +# +# arguments: argument list is built by os.path.walk function call +# arg - None +# dirname - directory location of files +# names - specific files to search in directory +# +# returns: none +# +def processUni(args, dirname, names): + global Options + # Remove excludedDirectory + if Options['ExcludeOption']: + for EachExDir in Options['ExPathList']: + for dir in names: + if os.path.join(dirname, dir) =3D=3D EachExDir: + names.remove(dir) + + for entry in names: + FullPath =3D dirname + os.sep + entry + if fnmatch.fnmatch(FullPath, '*.uni'): + parsefile(dirname, entry) + if fnmatch.fnmatch(FullPath, '*.vf*'): + searchVfiFile(FullPath) + if fnmatch.fnmatch(FullPath, '*.sd'): + searchVfiFile(FullPath) + if fnmatch.fnmatch(FullPath, '*.sdi'): + searchVfiFile(FullPath) + if fnmatch.fnmatch(FullPath, '*.hfr'): + searchVfiFile(FullPath) + return + + +# ********************************************************************** +# description: Compose a dictionary of all strings that may need UQIs assi= gned +# to them and key is the string +# +# arguments: Filename - name of file to search for strings +# +# returns: none +# + +# separate regexes for readability +StringGroups =3D re.compile( + '^[ \t]*(?:oneof|numeric|checkbox|orderedlist)[ \t]+varid.+?(?:endoneo= f|endnumeric|endcheckbox|endorderedlist);', + re.DOTALL | re.M).findall +StringVarIds =3D re.compile( + '[ \t]*(?:oneof|numeric|checkbox|orderedlist)[ \t]+varid[ \t]*=3D[ \t]= *([a-zA-Z_0-9]+\.[a-zA-Z_0-9]+)').findall +StringTokens =3D re.compile('prompt[ \t]*=3D[ \t]*STRING_TOKEN[ \t]*\(([a-= zA-Z_0-9]+)\)').findall + + +def searchVfiFile(Filename): + global Options, GlobalVarId, StringGroups, StringVarIds, StringTokens,= QuestionError + try: + with open(Filename, 'r') as VfiFile: + Databuffer =3D VfiFile.read() + + # Finds specified lines in file + VfiStringGroup =3D StringGroups(Databuffer) + + # Searches for prompts within specified lines + for EachGroup in VfiStringGroup: + for EachString in StringTokens(EachGroup): + # Ignore strings defined as STRING_TOKEN(0), STRING_TOKEN(= STR_EMPTY) or STRING_TOKEN(STR_NULL) + if (EachString !=3D "0") and (EachString !=3D "STR_EMPTY")= and (EachString !=3D "STR_NULL"): + if EachString not in GlobalVarId: + GlobalVarId[EachString] =3D StringVarIds(EachGroup= ) + else: + if (GlobalVarId[EachString][0] !=3D StringVarIds(E= achGroup)[0]): + if Options['QuestionOption']: + if Options['QuestionOption'] =3D=3D "e": + QuestionError =3D True + print("ERROR:"), + if Options['QuestionOption'] =3D=3D "w": + print("WARNING:"), + print("%s referred by different HII questi= ons(%s and %s)" % ( + EachString, GlobalVarId[EachString][0]= , StringVarIds(EachGroup)[0])) + except: + print("Error opening file at %s for reading." % Filename) + + +if __name__ =3D=3D '__main__': + sys.exit(main()) --=20 2.14.1.windows.1 --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF84C58SHSMSX101ccrcor_ Content-Disposition: attachment; filename="winmail.dat" Content-Transfer-Encoding: base64 Content-Type: application/ms-tnef; name="winmail.dat" eJ8+InBzAQaQCAAEAAAAAAABAAEAAQeQBgAIAAAA5AQAAAAAAADoAAEJgAEAIQAAAEY0MjcyRjhC Nzc1QkZENDY5RUVEODY1NzUwMDQ4MkFEAEcHAQ2ABAACAAAAAgACAAEFgAMADgAAAOMHBgAaAAUA FQAhAAMASAEBIIADAA4AAADjBwYAGgAFABUAIQADAEgBAQiABwAYAAAASVBNLk1pY3Jvc29mdCBN YWlsLk5vdGUAMQgBBIABAF0AAABbZWRrMi1wbGF0Zm9ybSBwYXRjaCBWNF0gUGxhdGZvcm0vSW50 ZWw6QWRkIFVuaVRvb2wgaW50byBlZGsyLXBsYXRmb3Jtcy9QbGF0Zm9ybS9JbnRlbC9Ub29scwCL IQELgAEAIQAAAEY0MjcyRjhCNzc1QkZENDY5RUVEODY1NzUwMDQ4MkFEAEcHAQOQBgCMMAAAMwAA AAIBfwABAAAASAAAADxGQUQwRDdFMEFFMEZBNTREOTg3RjZFNzI0MzVDQUZENTBBRjg0QzU4QFNI U01TWDEwMS5jY3IuY29ycC5pbnRlbC5jb20+AAsAHw4BAAAAAgEJEAEAAABjJgAAXyYAAJBtAABM WkZ1HpeO02EACmZiaWQEAABjY8BwZzEyNTIA/gND8HRleHQB9wKkA+MCAARjaArAc2V0MCDvB20C gwBQEU0yCoAGtAKAln0KgAjIOwliMTkOwL8JwxZyCjIWcQKAFWIqCbBzCfAEkGF0BbIOUANgc6Jv AYAgRXgRwW4YMF0GUnYEkBe2AhByAMB0fQhQbhoxECAFwAWgG2RkmiADUiAQIheyXHYIkOR3awuA ZDUdUwTwB0ANF3AwCnEX8mJrbWsGcwGQACAgQk1fQuBFR0lOfQr8AfEL8REfsFo6aAJAcHM6wC8v YnVnegMQC2AkLnQHMG5vBaFlLgEFsGcvc2hvd19BIlEuY2dpPw3QPagxODUd4GwLgGUKgaUlFFUD AFRvBvAgBADGIAIgGeBweXQj0AOgGwT1GJAgGDUZ4FVRSSwgKCXhGjFzB0AgUdsKUB9gaQIgJQVJ AQACMOMGkAiRKSB1AwAFoAEAPScwdAUQGcAccAWxSEkFKJBxKXUgUFJPTfRQVCuFLiV2KIErlR5Q 9QOgYhngdRIAHGAnsQ3Q0SpzeSBlANBoLDst5rklBVRoK3EFAwQgZisQ3mMswwPwIqAnMHkzcCsA 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 x7QPtRcQwiJlIrWvu2/nzxl4a4WxVHJ4cLp/v3+B079FUlJPUjrXcN9c+8Lvt4+4n7mod7pfx9/D wH/UoFdBUk5tAcH/+8ufyR8lfND1oNFhZJHvQgcMoNFS7jJISUkgcV94dYaQzcFG4s3AKdWgJf+t 4Mrf0i+qeJA4dNuuvXaM77CtqasEIVEAdMbvwTa9w397gpCi4/NDYc3BUgaFMS6f0NJyl2//0TUF 8V9f+KJ735AQwiffkDQw0UDfkCe52UtzeRlABCB7QCjgghuAQKmmLfMQ0SUyLjFONOPg7/Bo8W93 GUAxM9El0SV9fdFw5fAAHwBCAAEAAAAYAAAARgBhAG4ALAAgAFoAaABpAGoAdQBYAAAAHwBlAAEA AAAqAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAAAAfAGQAAQAA AAoAAABTAE0AVABQAAAAAAACAUEAAQAAAGQAAAAAAAAAgSsfpL6jEBmdbgDdAQ9UAgAAAIBGAGEA bgAsACAAWgBoAGkAagB1AFgAAABTAE0AVABQAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0 AGUAbAAuAGMAbwBtAAAAHwACXQEAAAAqAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUA bAAuAGMAbwBtAAAAAAAfAOVfAQAAADIAAABzAGkAcAA6AHoAaABpAGoAdQB4AC4AZgBhAG4AQABp AG4AdABlAGwALgBjAG8AbQAAAAAAHwAaDAEAAAAYAAAARgBhAG4ALAAgAFoAaABpAGoAdQBYAAAA HwAfDAEAAAAqAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAAAAf AB4MAQAAAAoAAABTAE0AVABQAAAAAAACARkMAQAAAGQAAAAAAAAAgSsfpL6jEBmdbgDdAQ9UAgAA AIBGAGEAbgAsACAAWgBoAGkAagB1AFgAAABTAE0AVABQAAAAegBoAGkAagB1AHgALgBmAGEAbgBA AGkAbgB0AGUAbAAuAGMAbwBtAAAAHwABXQEAAAAqAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkA bgB0AGUAbAAuAGMAbwBtAAAAAAAfAPg/AQAAABgAAABGAGEAbgAsACAAWgBoAGkAagB1AFgAAAAf ACNAAQAAACoAAAB6AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAAAB8A IkABAAAACgAAAFMATQBUAFAAAAAAAAIB+T8BAAAAZAAAAAAAAACBKx+kvqMQGZ1uAN0BD1QCAAAA gEYAYQBuACwAIABaAGgAaQBqAHUAWAAAAFMATQBUAFAAAAB6AGgAaQBqAHUAeAAuAGYAYQBuAEAA aQBuAHQAZQBsAC4AYwBvAG0AAAAfAAldAQAAACoAAAB6AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBu AHQAZQBsAC4AYwBvAG0AAAAAAAsAQDoBAAAAHwAaAAEAAAASAAAASQBQAE0ALgBOAG8AdABlAAAA AAADAPE/CQQAAAsAQDoBAAAAAwD9P+QEAAACAQswAQAAABAAAAD0Jy+Ld1v9Rp7thldQBIKtAwAX AAEAAABAADkAgGx4BN8r1QFAAAgwxGODBN8r1QEfAACAhgMCAAAAAADAAAAAAAAARgEAAAAeAAAA YQBjAGMAZQBwAHQAbABhAG4AZwB1AGEAZwBlAAAAAAABAAAADAAAAGUAbgAtAFUAUwAAAB8ANwAB AAAAugAAAFsAZQBkAGsAMgAtAHAAbABhAHQAZgBvAHIAbQAgAHAAYQB0AGMAaAAgAFYANABdACAA UABsAGEAdABmAG8AcgBtAC8ASQBuAHQAZQBsADoAQQBkAGQAIABVAG4AaQBUAG8AbwBsACAAaQBu AHQAbwAgAGUAZABrADIALQBwAGwAYQB0AGYAbwByAG0AcwAvAFAAbABhAHQAZgBvAHIAbQAvAEkA bgB0AGUAbAAvAFQAbwBvAGwAcwAAAAAAHwA9AAEAAAACAAAAAAAAAAMANgAAAAAAAgFxAAEAAAAW AAAAAdUr3wOYPeDSy9PbQpOHVGooTPJOhwAAHwBwAAEAAAC6AAAAWwBlAGQAawAyAC0AcABsAGEA dABmAG8AcgBtACAAcABhAHQAYwBoACAAVgA0AF0AIABQAGwAYQB0AGYAbwByAG0ALwBJAG4AdABl AGwAOgBBAGQAZAAgAFUAbgBpAFQAbwBvAGwAIABpAG4AdABvACAAZQBkAGsAMgAtAHAAbABhAHQA ZgBvAHIAbQBzAC8AUABsAGEAdABmAG8AcgBtAC8ASQBuAHQAZQBsAC8AVABvAG8AbABzAAAAAAAf ADUQAQAAAJAAAAA8AEYAQQBEADAARAA3AEUAMABBAEUAMABGAEEANQA0AEQAOQA4ADcARgA2AEUA NwAyADQAMwA1AEMAQQBGAEQANQAwAEEARgA4ADQAQwA1ADgAQABTAEgAUwBNAFMAWAAxADAAMQAu AGMAYwByAC4AYwBvAHIAcAAuAGkAbgB0AGUAbAAuAGMAbwBtAD4AAAADAN4/n04AAEAABzAEoX4E 3yvVAQIBCwABAAAAEAAAAPQnL4t3W/1Gnu2GV1AEgq0DACYAAAAAAAIBRwABAAAAMwAAAGM9VVM7 YT1NQ0k7cD1JbnRlbDtsPVNIU01TWDEwMS0xOTA2MjYwNTIxMzNaLTEyNDI0AAACARAwAQAAAEYA AAAAAAAAJne9OTvsOEmkphU9y6V7QgcA+tDX4K4PpU2Yf25yQ1yv1QAAAEQRXgAAppNoNoh2bEun StPrR5pMZAAACVSPywAAAAAfAPo/AQAAABgAAABGAGEAbgAsACAAWgBoAGkAagB1AFgAAAADAAlZ AQAAAEAAAIAIIAYAAAAAAMAAAAAAAABGAAAAAL+FAACgj5oD3yvVAQsAAIAIIAYAAAAAAMAAAAAA AABGAAAAAIKFAAAAAAAAHwAAgIYDAgAAAAAAwAAAAAAAAEYBAAAAGAAAAGQAbABwAC0AcAByAG8A ZAB1AGMAdAAAAAEAAAAaAAAAZABsAHAAZQAtAHcAaQBuAGQAbwB3AHMAAAAAAB8AAICGAwIAAAAA AMAAAAAAAABGAQAAABgAAABkAGwAcAAtAHYAZQByAHMAaQBvAG4AAAABAAAAFgAAADEAMQAuADAA LgA2ADAAMAAuADcAAAAAAB8AAICGAwIAAAAAAMAAAAAAAABGAQAAABoAAABkAGwAcAAtAHIAZQBh AGMAdABpAG8AbgAAAAAAAQAAABQAAABuAG8ALQBhAGMAdABpAG8AbgAAAAMADTT9PwAAHwAAgIYD AgAAAAAAwAAAAAAAAEYBAAAAIAAAAHgALQBtAHMALQBoAGEAcwAtAGEAdAB0AGEAYwBoAAAAAQAA AAIAAAAAAAAAHwAAgIYDAgAAAAAAwAAAAAAAAEYBAAAAIgAAAHgALQBvAHIAaQBnAGkAbgBhAHQA aQBuAGcALQBpAHAAAAAAAAEAAAAgAAAAWwAxADAALgAyADMAOQAuADEAMgA3AC4ANAAwAF0AAAA6 oA== --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF84C58SHSMSX101ccrcor_--