From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: mx.groups.io; dkim=missing; spf=pass (domain: intel.com, ip: 192.55.52.136, mailfrom: zhijux.fan@intel.com) Received: from mga12.intel.com (mga12.intel.com [192.55.52.136]) by groups.io with SMTP; Wed, 19 Jun 2019 17:31:49 -0700 X-Amp-Result: UNKNOWN X-Amp-Original-Verdict: FILE UNKNOWN X-Amp-File-Uploaded: False Received: from fmsmga008.fm.intel.com ([10.253.24.58]) by fmsmga106.fm.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 19 Jun 2019 17:31:48 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.63,394,1557212400"; d="dat'59?scan'59,208,59";a="160512644" Received: from fmsmsx103.amr.corp.intel.com ([10.18.124.201]) by fmsmga008.fm.intel.com with ESMTP; 19 Jun 2019 17:31:48 -0700 Received: from fmsmsx161.amr.corp.intel.com (10.18.125.9) by FMSMSX103.amr.corp.intel.com (10.18.124.201) with Microsoft SMTP Server (TLS) id 14.3.439.0; Wed, 19 Jun 2019 17:31:48 -0700 Received: from shsmsx106.ccr.corp.intel.com (10.239.4.159) by FMSMSX161.amr.corp.intel.com (10.18.125.9) with Microsoft SMTP Server (TLS) id 14.3.439.0; Wed, 19 Jun 2019 17:31:47 -0700 Received: from shsmsx101.ccr.corp.intel.com ([169.254.1.87]) by SHSMSX106.ccr.corp.intel.com ([169.254.10.89]) with mapi id 14.03.0439.000; Thu, 20 Jun 2019 08:31:44 +0800 From: "Fan, ZhijuX" To: "devel@edk2.groups.io" CC: "Gao, Liming" , "Feng, Bob C" Subject: [PATCH V3] BaseTools:add UniTool.py to Edk2\BaseTools\Scripts Thread-Topic: [PATCH V3] BaseTools:add UniTool.py to Edk2\BaseTools\Scripts Thread-Index: AdUm/4f1gJt/j72LQ7uu/atfmHsw0A== Date: Thu, 20 Jun 2019 00:31:43 +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: 42606 Content-Type: multipart/mixed; boundary="_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF83D24SHSMSX101ccrcor_" Content-Language: en-US --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF83D24SHSMSX101ccrcor_ 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: Bob Feng Cc: Liming Gao Signed-off-by: Zhiju.Fan --- BaseTools/Scripts/UniTool.py | 514 +++++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 514 insertions(+) create mode 100644 BaseTools/Scripts/UniTool.py diff --git a/BaseTools/Scripts/UniTool.py b/BaseTools/Scripts/UniTool.py new file mode 100644 index 0000000000..1064e261f7 --- /dev/null +++ b/BaseTools/Scripts/UniTool.py @@ -0,0 +1,514 @@ +## @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 ba= sed 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 = needs assigning + based on vfi/vfr/hfr/sd/sdi when no -u opt= ion is specified + -u Create new UQIs that does not already exis= t in uqiFile for + any string requiring a UQI based on vfi/vf= r/hfr/sd/sdi + NOTE: 'uqiFile' cannot be readonly! + -l Language deletion option (keeps only Engli= sh and uqi) + moves all UQIs to 'uqiFile' + NOTE: Uni files cannot be readonly! + -x Exclude 'rootDirectory'/'excludedDirectory= 1' & + 'rootDirectory'/'excludedDirectory2'... fr= om 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 dif= ferent 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) + ReturnVal =3D 0 + 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 at= tibute 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], St= ringVarIds(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_FAD0D7E0AE0FA54D987F6E72435CAFD50AF83D24SHSMSX101ccrcor_ Content-Disposition: attachment; filename="winmail.dat" Content-Transfer-Encoding: base64 Content-Type: application/ms-tnef; name="winmail.dat" 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 puTRLp8wctI3z58w1WWRX19YQns+8HBiJz7wk9DIkD7wJ7k4q3N5btBjwNrgKD/i1ihBIk2Bdjdn LVKwMMVwMi4xNEPAT5DIkW+ad27QMTDFMMV9fTEQAUXQAAAAHwBCAAEAAAAYAAAARgBhAG4ALAAg AFoAaABpAGoAdQBYAAAAHwBlAAEAAAAqAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUA bAAuAGMAbwBtAAAAAAAfAGQAAQAAAAoAAABTAE0AVABQAAAAAAACAUEAAQAAAGQAAAAAAAAAgSsf pL6jEBmdbgDdAQ9UAgAAAIBGAGEAbgAsACAAWgBoAGkAagB1AFgAAABTAE0AVABQAAAAegBoAGkA agB1AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAHwACXQEAAAAqAAAAegBoAGkAagB1 AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAAAAfAOVfAQAAADIAAABzAGkAcAA6AHoA aABpAGoAdQB4AC4AZgBhAG4AQABpAG4AdABlAGwALgBjAG8AbQAAAAAAHwAaDAEAAAAYAAAARgBh AG4ALAAgAFoAaABpAGoAdQBYAAAAHwAfDAEAAAAqAAAAegBoAGkAagB1AHgALgBmAGEAbgBAAGkA bgB0AGUAbAAuAGMAbwBtAAAAAAAfAB4MAQAAAAoAAABTAE0AVABQAAAAAAACARkMAQAAAGQAAAAA AAAAgSsfpL6jEBmdbgDdAQ9UAgAAAIBGAGEAbgAsACAAWgBoAGkAagB1AFgAAABTAE0AVABQAAAA egBoAGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAHwABXQEAAAAqAAAAegBo AGkAagB1AHgALgBmAGEAbgBAAGkAbgB0AGUAbAAuAGMAbwBtAAAAAAAfAPg/AQAAABgAAABGAGEA bgAsACAAWgBoAGkAagB1AFgAAAAfACNAAQAAACoAAAB6AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBu AHQAZQBsAC4AYwBvAG0AAAAAAB8AIkABAAAACgAAAFMATQBUAFAAAAAAAAIB+T8BAAAAZAAAAAAA AACBKx+kvqMQGZ1uAN0BD1QCAAAAgEYAYQBuACwAIABaAGgAaQBqAHUAWAAAAFMATQBUAFAAAAB6 AGgAaQBqAHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAfAAldAQAAACoAAAB6AGgA aQBqAHUAeAAuAGYAYQBuAEAAaQBuAHQAZQBsAC4AYwBvAG0AAAAAAAsAQDoBAAAAHwAaAAEAAAAS AAAASQBQAE0ALgBOAG8AdABlAAAAAAADAPE/CQQAAAsAQDoBAAAAAwD9P+QEAAACAQswAQAAABAA AAAQQCI3gXcaRa2p/zurQmH/AwAXAAEAAABAADkAgIG+iP8m1QFAAAgw8cwbif8m1QEfAACAhgMC AAAAAADAAAAAAAAARgEAAAAeAAAAYQBjAGMAZQBwAHQAbABhAG4AZwB1AGEAZwBlAAAAAAABAAAA DAAAAGUAbgAtAFUAUwAAAB8ANwABAAAAfgAAAFsAUABBAFQAQwBIACAAVgAzAF0AIAAgAEIAYQBz AGUAVABvAG8AbABzADoAYQBkAGQAIABVAG4AaQBUAG8AbwBsAC4AcAB5ACAAdABvACAARQBkAGsA MgBcAEIAYQBzAGUAVABvAG8AbABzAFwAUwBjAHIAaQBwAHQAcwAAAAAAHwA9AAEAAAACAAAAAAAA AAMANgAAAAAAAgFxAAEAAAAWAAAAAdUm/4f1gJt/j72LQ7uu/atfmHsw0AAAHwBwAAEAAAB+AAAA WwBQAEEAVABDAEgAIABWADMAXQAgACAAQgBhAHMAZQBUAG8AbwBsAHMAOgBhAGQAZAAgAFUAbgBp AFQAbwBvAGwALgBwAHkAIAB0AG8AIABFAGQAawAyAFwAQgBhAHMAZQBUAG8AbwBsAHMAXABTAGMA cgBpAHAAdABzAAAAAAAfADUQAQAAAJAAAAA8AEYAQQBEADAARAA3AEUAMABBAEUAMABGAEEANQA0 AEQAOQA4ADcARgA2AEUANwAyADQAMwA1AEMAQQBGAEQANQAwAEEARgA4ADMARAAyADQAQABTAEgA UwBNAFMAWAAxADAAMQAuAGMAYwByAC4AYwBvAHIAcAAuAGkAbgB0AGUAbAAuAGMAbwBtAD4AAAAD AN4/n04AAEAABzBxRxKJ/ybVAQIBCwABAAAAEAAAABBAIjeBdxpFran/O6tCYf8DACYAAAAAAAIB RwABAAAAMgAAAGM9VVM7YT1NQ0k7cD1JbnRlbDtsPVNIU01TWDEwMS0xOTA2MjAwMDMxNDNaLTcy MjIAAAACARAwAQAAAEYAAAAAAAAAJne9OTvsOEmkphU9y6V7QgcA+tDX4K4PpU2Yf25yQ1yv1QAA AEQRXgAAppNoNoh2bEunStPrR5pMZAAACVSPtgAAAAAfAPo/AQAAABgAAABGAGEAbgAsACAAWgBo AGkAagB1AFgAAAADAAlZAQAAAEAAAIAIIAYAAAAAAMAAAAAAAABGAAAAAL+FAAAgXguI/ybVAQsA AIAIIAYAAAAAAMAAAAAAAABGAAAAAIKFAAAAAAAAAwAAgAggBgAAAAAAwAAAAAAAAEYAAAAA64UA AAkEAAAfAACAhgMCAAAAAADAAAAAAAAARgEAAAAYAAAAZABsAHAALQBwAHIAbwBkAHUAYwB0AAAA AQAAABoAAABkAGwAcABlAC0AdwBpAG4AZABvAHcAcwAAAAAAHwAAgIYDAgAAAAAAwAAAAAAAAEYB AAAAGAAAAGQAbABwAC0AdgBlAHIAcwBpAG8AbgAAAAEAAAAWAAAAMQAxAC4AMAAuADYAMAAwAC4A NwAAAAAAHwAAgIYDAgAAAAAAwAAAAAAAAEYBAAAAGgAAAGQAbABwAC0AcgBlAGEAYwB0AGkAbwBu AAAAAAABAAAAFAAAAG4AbwAtAGEAYwB0AGkAbwBuAAAAAwANNP0/AAAfAACAhgMCAAAAAADAAAAA AAAARgEAAAAgAAAAeAAtAG0AcwAtAGgAYQBzAC0AYQB0AHQAYQBjAGgAAAABAAAAAgAAAAAAAAAf AACAhgMCAAAAAADAAAAAAAAARgEAAAAiAAAAeAAtAG8AcgBpAGcAaQBuAGEAdABpAG4AZwAtAGkA cAAAAAAAAQAAACAAAABbADEAMAAuADIAMwA5AC4AMQAyADcALgA0ADAAXQAAANeJ --_000_FAD0D7E0AE0FA54D987F6E72435CAFD50AF83D24SHSMSX101ccrcor_--