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.65, mailfrom: zhijux.fan@intel.com) Received: from mga03.intel.com (mga03.intel.com [134.134.136.65]) by groups.io with SMTP; Thu, 20 Jun 2019 18:59:02 -0700 X-Amp-Result: UNKNOWN X-Amp-Original-Verdict: FILE UNKNOWN X-Amp-File-Uploaded: False Received: from fmsmga001.fm.intel.com ([10.253.24.23]) by orsmga103.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 20 Jun 2019 18:59:01 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.63,398,1557212400"; d="dat'59?scan'59,208,59";a="181991341" Received: from fmsmsx105.amr.corp.intel.com ([10.18.124.203]) by fmsmga001.fm.intel.com with ESMTP; 20 Jun 2019 18:59:01 -0700 Received: from fmsmsx162.amr.corp.intel.com (10.18.125.71) by FMSMSX105.amr.corp.intel.com (10.18.124.203) with Microsoft SMTP Server (TLS) id 14.3.439.0; Thu, 20 Jun 2019 18:59:01 -0700 Received: from shsmsx151.ccr.corp.intel.com (10.239.6.50) by fmsmsx162.amr.corp.intel.com (10.18.125.71) with Microsoft SMTP Server (TLS) id 14.3.439.0; Thu, 20 Jun 2019 18:59:00 -0700 Received: from shsmsx101.ccr.corp.intel.com ([169.254.1.87]) by SHSMSX151.ccr.corp.intel.com ([169.254.3.246]) with mapi id 14.03.0439.000; Fri, 21 Jun 2019 09:58:58 +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 2/2] Platform/Intel:Add UniTool into edk2-platforms/Platform/Intel/Tools Thread-Topic: [edk2-platform patch 2/2] Platform/Intel:Add UniTool into edk2-platforms/Platform/Intel/Tools Thread-Index: AdUn1OLEUhPU+i5FRWas19TdlxjT4Q== Date: Fri, 21 Jun 2019 01:58:57 +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: 42677 Content-Type: multipart/mixed; boundary="_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF84144SHSMSX101ccrcor_" Content-Language: en-US --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF84144SHSMSX101ccrcor_ 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 --- Platform/Intel/Tools/UniTool/UniTool.py | 515 ++++++++++++++++++++++++++++= ++++ 1 file changed, 515 insertions(+) create mode 100644 Platform/Intel/Tools/UniTool/UniTool.py diff --git a/Platform/Intel/Tools/UniTool/UniTool.py b/Platform/Intel/Tools= /UniTool/UniTool.py new file mode 100644 index 0000000000..aec25b51c2 --- /dev/null +++ b/Platform/Intel/Tools/UniTool/UniTool.py @@ -0,0 +1,515 @@ +## @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 + +# 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 {} + + +# ********************************************************************** +# description: Prints help information +# +# arguments: none +# +# returns: none +# + +def Usage(): + print("Syntax: %s [-b] [-u] [-l] [-x] [-h] [-d 'rootDirectory1'] [-d = 'rootDirectory2'] [-d 'rootDirectory3']... [-q e|w] \ +'rootDirectory0' 'uqiFile'|'uqiFileDirectory' ['excludedDirectory1'] ['exc= ludedDirectory2'] ['excludedDirectory3']...\n%s" % + (os.path.basename(sys.argv[0]), + """\nFunction will sync up UQI definitions with uni files based= on vfi/vfr/hfr/sd/sdi in the tree.\n + 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 nee= ds assigning + based on vfi/vfr/hfr/sd/sdi when no -u option= is specified + -u Create new UQIs that does not already exist i= n uqiFile for + any string requiring a UQI based on vfi/vfr/h= fr/sd/sdi + NOTE: 'uqiFile' cannot be readonly! + -l Language deletion option (keeps only English = and uqi) + moves all UQIs to 'uqiFile' + NOTE: Uni files cannot be readonly! + -x Exclude 'rootDirectory'/'excludedDirectory1' = & + 'rootDirectory'/'excludedDirectory2'... from = UQI list build + NOTE: Cannot be the same as rootDirectory + -d Add multiple root directories to process + -q Print warning(w) or return error(e) if differ= ent HII questions + are referring same string token + + Return error if any duplicated UQI string or value in UQI list or if = no definition + for any string referred by HII question when -b or -u is specified + + NOTE: Options must be specified before parameters + """)) + sys.exit() + + +# ********************************************************************** +# 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(argv): + ##### Read input arguments and options + global AllUqis, UqiList, QuestionError + try: + Opts, Args =3D getopt.getopt(argv[1:], "hulbxd:q:") # each letter= is an optional argument + except getopt.GetoptError: + Usage() + try: + DirNameList =3D [Args[0]] + QuestionOption =3D None + for EachOpt in Opts: + if EachOpt[0] =3D=3D '-d': + DirNameList.append(EachOpt[1]) + if EachOpt[0] =3D=3D '-q': + QuestionOption =3D EachOpt[1] + if (QuestionOption !=3D "e") and (QuestionOption !=3D "w")= : + print("\nERROR: invalid option value for -q option\n") + return + Destname =3D Args[1] + if len(Args) > 2: + ExDirList =3D Args[2:] + except: + Usage() + + UpdateUQIs =3D False + LangOption =3D False + BuildOption =3D False + ExcludeOption =3D False + ExPathList =3D [] + + for Opt, Args in Opts: + if Opt =3D=3D "-h": + Usage() + if Opt =3D=3D "-b": + BuildOption =3D True + if Opt =3D=3D "-u": + BuildOption =3D True + UpdateUQIs =3D True + if Opt =3D=3D "-l": + LangOption =3D True + if Opt =3D=3D "-x": + ExcludeOption =3D True + try: + for EachExDir in ExDirList: + for EachRootDir in DirNameList: + if EachExDir =3D=3D EachRootDir: + print("\nERROR: excludedDirectory is same as r= ootDirectory\n") + return + ExPathList.append(EachRootDir + os.sep + EachExDir) + except: + Usage() + + 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(sys.argv)) --=20 2.14.1.windows.1 --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF84144SHSMSX101ccrcor_ Content-Disposition: attachment; filename="winmail.dat" Content-Transfer-Encoding: base64 Content-Type: application/ms-tnef; name="winmail.dat" eJ8+Ik5gAQaQCAAEAAAAAAABAAEAAQeQBgAIAAAA5AQAAAAAAADoAAEJgAEAIQAAAEU1REVBRjFD RDE2NTk2NDM5NjU4OENFODJGNDZGNUI0AFsHAQ2ABAACAAAAAgACAAEFgAMADgAAAOMHBgAVAAEA OgA5AAUAfgEBIIADAA4AAADjBwYAFQABADoAOQAFAH4BAQiABwAYAAAASVBNLk1pY3Jvc29mdCBN YWlsLk5vdGUAMQgBBIABAF4AAABbZWRrMi1wbGF0Zm9ybSBwYXRjaCAyLzJdIFBsYXRmb3JtL0lu dGVsOkFkZCBVbmlUb29sIGludG8gZWRrMi1wbGF0Zm9ybXMvUGxhdGZvcm0vSW50ZWwvVG9vbHMA lCEBC4ABACEAAABFNURFQUYxQ0QxNjU5NjQzOTY1ODhDRTgyRjQ2RjVCNABbBwEDkAYA6CwAADMA AAACAX8AAQAAAEgAAAA8RkFEMEQ3RTBBRTBGQTU0RDk4N0Y2RTcyNDM1Q0FGRDUwQUY4NDE0NEBT SFNNU1gxMDEuY2NyLmNvcnAuaW50ZWwuY29tPgALAB8OAQAAAAIBCRABAAAAvSIAALkiAAApZAAA TFpGdTGD9zxhAApmYmlkBAAAY2PAcGcxMjUyAP4DQ/B0ZXh0AfcCpAPjAgAEY2gKwHNldDAg7wdt AoMAUBFNMgqABrQCgJZ9CoAIyDsJYjE5DsC/CcMWcgoyFnECgBViKgmwcwnwBJBhdAWyDlADYHOi bwGAIEV4EcFuGDBdBlJ2BJAXtgIQcgDAdH0IUG4aMRAgBcAFoBtkZJogA1IgECIXslx2CJDkd2sL gGQ1HVME8AdADRdwMApxF/Jia21rBnMBkAAgIEJNX0LgRUdJTn0K/AHxC/ERH7BaOmgCQHBzOsAv L2J1Z3oDEAtgJC50BzBubwWhZS4BBbBnL3Nob3dfQSJRLmNnaT8N0D2oMTg1HeBsC4BlCoGlJRRV AwBUbwbwIAQAxiACIBngcHl0I9ADoBsE9RiQIBg1GeBVUUksICgl4RoxcwdAIFHbClAfYGkCICUF SQEAAjDjBpAIkSkgdQMABaABAD0nMHQFEBnAHHAFsUhJBSiQcSl1IFBST030UFQrhS4oYiUFK5Ue UPUDoGIZ4HUSABxgJ7EN0FEqc3kgZQDQaCw7LrklBVRoK3EFAwQgZisQ3mMswwPwIqAnMHkzACsA 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 jwMojzzPRVJST1I6v0CAxgsr/yCfIa8iuHcjb4cw7ymPPbBXQVJO1hH3Kw80rzIvJeXgokA6cc2h D1hSdbA6YldCSElJIL5x4YXvoDbRr/I20Ck+sP4lFvAz7zs/E4j5SN3rF83f35wZvRK7bTG6EHQv /ypG/ybT5JL5sk0DrHE20bsW7kE+Ljni26fZDzpFbwFfX/eB0kigedInSKCdQNIASKByJ0Jbc3l4 QG0w5FAorUmSKErSVvF2QRctXCDhOjUyLjE0TXBZANIBNG93eEAxOjU6NX19AzqAT4AAAAAfAEIA AQAAABgAAABGAGEAbgAsACAAWgBoAGkAagB1AFgAAAAfAGUAAQAAACoAAAB6AGgAaQBqAHUAeAAu AGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAAAB8AZAABAAAACgAAAFMATQBUAFAAAAAAAAIB QQABAAAAZAAAAAAAAACBKx+kvqMQGZ1uAN0BD1QCAAAAgEYAYQBuACwAIABaAGgAaQBqAHUAWAAA AFMATQBUAFAAAAB6AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAfAAJd AQAAACoAAAB6AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAAAB8A5V8B AAAAMgAAAHMAaQBwADoAegBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAA AAAfABoMAQAAABgAAABGAGEAbgAsACAAWgBoAGkAagB1AFgAAAAfAB8MAQAAACoAAAB6AGgAaQBq AHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAAAB8AHgwBAAAACgAAAFMATQBUAFAA AAAAAAIBGQwBAAAAZAAAAAAAAACBKx+kvqMQGZ1uAN0BD1QCAAAAgEYAYQBuACwAIABaAGgAaQBq AHUAWAAAAFMATQBUAFAAAAB6AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0A AAAfAAFdAQAAACoAAAB6AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAA AB8A+D8BAAAAGAAAAEYAYQBuACwAIABaAGgAaQBqAHUAWAAAAB8AI0ABAAAAKgAAAHoAaABpAGoA dQB4AC4AZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAAAAAAHwAiQAEAAAAKAAAAUwBNAFQAUAAA AAAAAgH5PwEAAABkAAAAAAAAAIErH6S+oxAZnW4A3QEPVAIAAACARgBhAG4ALAAgAFoAaABpAGoA dQBYAAAAUwBNAFQAUAAAAHoAaABpAGoAdQB4AC4AZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAA AB8ACV0BAAAAKgAAAHoAaABpAGoAdQB4AC4AZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAAAAAA CwBAOgEAAAAfABoAAQAAABIAAABJAFAATQAuAE4AbwB0AGUAAAAAAAMA8T8JBAAACwBAOgEAAAAD AP0/5AQAAAIBCzABAAAAEAAAAOXerxzRZZZDlliM6C9G9bQDABcAAQAAAEAAOQCARt3i1CfVAUAA CDA5M1Tj1CfVAR8AAICGAwIAAAAAAMAAAAAAAABGAQAAAB4AAABhAGMAYwBlAHAAdABsAGEAbgBn AHUAYQBnAGUAAAAAAAEAAAAMAAAAZQBuAC0AVQBTAAAAHwA3AAEAAAC8AAAAWwBlAGQAawAyAC0A cABsAGEAdABmAG8AcgBtACAAcABhAHQAYwBoACAAMgAvADIAXQAgAFAAbABhAHQAZgBvAHIAbQAv AEkAbgB0AGUAbAA6AEEAZABkACAAVQBuAGkAVABvAG8AbAAgAGkAbgB0AG8AIABlAGQAawAyAC0A cABsAGEAdABmAG8AcgBtAHMALwBQAGwAYQB0AGYAbwByAG0ALwBJAG4AdABlAGwALwBUAG8AbwBs AHMAAAAfAD0AAQAAAAIAAAAAAAAAAwA2AAAAAAACAXEAAQAAABYAAAAB1SfU4sRSE9T6LkVFZqzX 1N2XGNPhAAAfAHAAAQAAALwAAABbAGUAZABrADIALQBwAGwAYQB0AGYAbwByAG0AIABwAGEAdABj AGgAIAAyAC8AMgBdACAAUABsAGEAdABmAG8AcgBtAC8ASQBuAHQAZQBsADoAQQBkAGQAIABVAG4A aQBUAG8AbwBsACAAaQBuAHQAbwAgAGUAZABrADIALQBwAGwAYQB0AGYAbwByAG0AcwAvAFAAbABh AHQAZgBvAHIAbQAvAEkAbgB0AGUAbAAvAFQAbwBvAGwAcwAAAB8ANRABAAAAkAAAADwARgBBAEQA MABEADcARQAwAEEARQAwAEYAQQA1ADQARAA5ADgANwBGADYARQA3ADIANAAzADUAQwBBAEYARAA1 ADAAQQBGADgANAAxADQANABAAFMASABTAE0AUwBYADEAMAAxAC4AYwBjAHIALgBjAG8AcgBwAC4A aQBuAHQAZQBsAC4AYwBvAG0APgAAAAMA3j+fTgAAQAAHMNnRUePUJ9UBAgELAAEAAAAQAAAA5d6v HNFllkOWWIzoL0b1tAMAJgAAAAAAAgFHAAEAAAAyAAAAYz1VUzthPU1DSTtwPUludGVsO2w9U0hT TVNYMTAxLTE5MDYyMTAxNTg1N1otODM3NgAAAAIBEDABAAAARgAAAAAAAAAmd705O+w4SaSmFT3L pXtCBwD60Nfgrg+lTZh/bnJDXK/VAAAARBFeAACmk2g2iHZsS6dK0+tHmkxkAAAJVI+9AAAAAB8A +j8BAAAAGAAAAEYAYQBuACwAIABaAGgAaQBqAHUAWAAAAAMACVkBAAAAQAAAgAggBgAAAAAAwAAA AAAAAEYAAAAAv4UAABA8xuLUJ9UBCwAAgAggBgAAAAAAwAAAAAAAAEYAAAAAgoUAAAAAAAAfAACA hgMCAAAAAADAAAAAAAAARgEAAAAYAAAAZABsAHAALQBwAHIAbwBkAHUAYwB0AAAAAQAAABoAAABk AGwAcABlAC0AdwBpAG4AZABvAHcAcwAAAAAAHwAAgIYDAgAAAAAAwAAAAAAAAEYBAAAAGAAAAGQA bABwAC0AdgBlAHIAcwBpAG8AbgAAAAEAAAAWAAAAMQAxAC4AMAAuADYAMAAwAC4ANwAAAAAAHwAA gIYDAgAAAAAAwAAAAAAAAEYBAAAAGgAAAGQAbABwAC0AcgBlAGEAYwB0AGkAbwBuAAAAAAABAAAA FAAAAG4AbwAtAGEAYwB0AGkAbwBuAAAAAwANNP0/AAAfAACAhgMCAAAAAADAAAAAAAAARgEAAAAg AAAAeAAtAG0AcwAtAGgAYQBzAC0AYQB0AHQAYQBjAGgAAAABAAAAAgAAAAAAAAAfAACAhgMCAAAA AADAAAAAAAAARgEAAAAiAAAAeAAtAG8AcgBpAGcAaQBuAGEAdABpAG4AZwAtAGkAcAAAAAAAAQAA ACAAAABbADEAMAAuADIAMwA5AC4AMQAyADcALgA0ADAAXQAAAJce --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF84144SHSMSX101ccrcor_--