1## @file
2# This file is used to define common string related functions used in parsing
3# process
4#
5# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
6#
7# SPDX-License-Identifier: BSD-2-Clause-Patent
8#
9'''
10StringUtils
11'''
12##
13# Import Modules
14#
15import re
16import os.path
17import Logger.Log as Logger
18import Library.DataType as DataType
19from Logger.ToolError import FORMAT_INVALID
20from Logger.ToolError import PARSER_ERROR
21from Logger import StringTable as ST
22
23#
24# Regular expression for matching macro used in DSC/DEC/INF file inclusion
25#
26gMACRO_PATTERN = re.compile("\$\(([_A-Z][_A-Z0-9]*)\)", re.UNICODE)
27
28## GetSplitValueList
29#
30# Get a value list from a string with multiple values split with SplitTag
31# The default SplitTag is DataType.TAB_VALUE_SPLIT
32# 'AAA|BBB|CCC' -> ['AAA', 'BBB', 'CCC']
33#
34# @param String:    The input string to be splitted
35# @param SplitTag:  The split key, default is DataType.TAB_VALUE_SPLIT
36# @param MaxSplit:  The max number of split values, default is -1
37#
38#
39def GetSplitValueList(String, SplitTag=DataType.TAB_VALUE_SPLIT, MaxSplit= -1):
40    return list(map(lambda l: l.strip(), String.split(SplitTag, MaxSplit)))
41
42## MergeArches
43#
44# Find a key's all arches in dict, add the new arch to the list
45# If not exist any arch, set the arch directly
46#
47# @param Dict:  The input value for Dict
48# @param Key:   The input value for Key
49# @param Arch:  The Arch to be added or merged
50#
51def MergeArches(Dict, Key, Arch):
52    if Key in Dict.keys():
53        Dict[Key].append(Arch)
54    else:
55        Dict[Key] = Arch.split()
56
57## GenDefines
58#
59# Parse a string with format "DEFINE <VarName> = <PATH>"
60# Generate a map Defines[VarName] = PATH
61# Return False if invalid format
62#
63# @param String:   String with DEFINE statement
64# @param Arch:     Supported Arch
65# @param Defines:  DEFINE statement to be parsed
66#
67def GenDefines(String, Arch, Defines):
68    if String.find(DataType.TAB_DEFINE + ' ') > -1:
69        List = String.replace(DataType.TAB_DEFINE + ' ', '').\
70        split(DataType.TAB_EQUAL_SPLIT)
71        if len(List) == 2:
72            Defines[(CleanString(List[0]), Arch)] = CleanString(List[1])
73            return 0
74        else:
75            return -1
76    return 1
77
78## GetLibraryClassesWithModuleType
79#
80# Get Library Class definition when no module type defined
81#
82# @param Lines:             The content to be parsed
83# @param Key:               Reserved
84# @param KeyValues:         To store data after parsing
85# @param CommentCharacter:  Comment char, used to ignore comment content
86#
87def GetLibraryClassesWithModuleType(Lines, Key, KeyValues, CommentCharacter):
88    NewKey = SplitModuleType(Key)
89    Lines = Lines.split(DataType.TAB_SECTION_END, 1)[1]
90    LineList = Lines.splitlines()
91    for Line in LineList:
92        Line = CleanString(Line, CommentCharacter)
93        if Line != '' and Line[0] != CommentCharacter:
94            KeyValues.append([CleanString(Line, CommentCharacter), NewKey[1]])
95
96    return True
97
98## GetDynamics
99#
100# Get Dynamic Pcds
101#
102# @param Lines:             The content to be parsed
103# @param Key:               Reserved
104# @param KeyValues:         To store data after parsing
105# @param CommentCharacter:  Comment char, used to ignore comment content
106#
107def GetDynamics(Lines, Key, KeyValues, CommentCharacter):
108    #
109    # Get SkuId Name List
110    #
111    SkuIdNameList = SplitModuleType(Key)
112
113    Lines = Lines.split(DataType.TAB_SECTION_END, 1)[1]
114    LineList = Lines.splitlines()
115    for Line in LineList:
116        Line = CleanString(Line, CommentCharacter)
117        if Line != '' and Line[0] != CommentCharacter:
118            KeyValues.append([CleanString(Line, CommentCharacter), SkuIdNameList[1]])
119
120    return True
121
122## SplitModuleType
123#
124# Split ModuleType out of section defien to get key
125# [LibraryClass.Arch.ModuleType|ModuleType|ModuleType] -> [
126# 'LibraryClass.Arch', ['ModuleType', 'ModuleType', 'ModuleType'] ]
127#
128# @param Key:  String to be parsed
129#
130def SplitModuleType(Key):
131    KeyList = Key.split(DataType.TAB_SPLIT)
132    #
133    # Fill in for arch
134    #
135    KeyList.append('')
136    #
137    # Fill in for moduletype
138    #
139    KeyList.append('')
140    ReturnValue = []
141    KeyValue = KeyList[0]
142    if KeyList[1] != '':
143        KeyValue = KeyValue + DataType.TAB_SPLIT + KeyList[1]
144    ReturnValue.append(KeyValue)
145    ReturnValue.append(GetSplitValueList(KeyList[2]))
146
147    return ReturnValue
148
149## Replace macro in string
150#
151# This method replace macros used in given string. The macros are given in a
152# dictionary.
153#
154# @param String             String to be processed
155# @param MacroDefinitions   The macro definitions in the form of dictionary
156# @param SelfReplacement    To decide whether replace un-defined macro to ''
157# @param Line:              The content contain line string and line number
158# @param FileName:        The meta-file file name
159#
160def ReplaceMacro(String, MacroDefinitions=None, SelfReplacement=False, Line=None, FileName=None, Flag=False):
161    LastString = String
162    if MacroDefinitions is None:
163        MacroDefinitions = {}
164    while MacroDefinitions:
165        QuotedStringList = []
166        HaveQuotedMacroFlag = False
167        if not Flag:
168            MacroUsed = gMACRO_PATTERN.findall(String)
169        else:
170            ReQuotedString = re.compile('\"')
171            QuotedStringList = ReQuotedString.split(String)
172            if len(QuotedStringList) >= 3:
173                HaveQuotedMacroFlag = True
174            Count = 0
175            MacroString = ""
176            for QuotedStringItem in QuotedStringList:
177                Count += 1
178                if Count % 2 != 0:
179                    MacroString += QuotedStringItem
180
181                if Count == len(QuotedStringList) and Count % 2 == 0:
182                    MacroString += QuotedStringItem
183
184            MacroUsed = gMACRO_PATTERN.findall(MacroString)
185        #
186        # no macro found in String, stop replacing
187        #
188        if len(MacroUsed) == 0:
189            break
190        for Macro in MacroUsed:
191            if Macro not in MacroDefinitions:
192                if SelfReplacement:
193                    String = String.replace("$(%s)" % Macro, '')
194                    Logger.Debug(5, "Delete undefined MACROs in file %s line %d: %s!" % (FileName, Line[1], Line[0]))
195                continue
196            if not HaveQuotedMacroFlag:
197                String = String.replace("$(%s)" % Macro, MacroDefinitions[Macro])
198            else:
199                Count = 0
200                for QuotedStringItem in QuotedStringList:
201                    Count += 1
202                    if Count % 2 != 0:
203                        QuotedStringList[Count - 1] = QuotedStringList[Count - 1].replace("$(%s)" % Macro,
204                                                                        MacroDefinitions[Macro])
205                    elif Count == len(QuotedStringList) and Count % 2 == 0:
206                        QuotedStringList[Count - 1] = QuotedStringList[Count - 1].replace("$(%s)" % Macro,
207                                                                        MacroDefinitions[Macro])
208
209        RetString = ''
210        if HaveQuotedMacroFlag:
211            Count = 0
212            for QuotedStringItem in QuotedStringList:
213                Count += 1
214                if Count != len(QuotedStringList):
215                    RetString += QuotedStringList[Count - 1] + "\""
216                else:
217                    RetString += QuotedStringList[Count - 1]
218
219            String = RetString
220
221        #
222        # in case there's macro not defined
223        #
224        if String == LastString:
225            break
226        LastString = String
227
228    return String
229
230## NormPath
231#
232# Create a normal path
233# And replace DEFINE in the path
234#
235# @param Path:     The input value for Path to be converted
236# @param Defines:  A set for DEFINE statement
237#
238def NormPath(Path, Defines=None):
239    IsRelativePath = False
240    if Defines is None:
241        Defines = {}
242    if Path:
243        if Path[0] == '.':
244            IsRelativePath = True
245        #
246        # Replace with Define
247        #
248        if Defines:
249            Path = ReplaceMacro(Path, Defines)
250        #
251        # To local path format
252        #
253        Path = os.path.normpath(Path)
254
255    if IsRelativePath and Path[0] != '.':
256        Path = os.path.join('.', Path)
257    return Path
258
259## CleanString
260#
261# Remove comments in a string
262# Remove spaces
263#
264# @param Line:              The string to be cleaned
265# @param CommentCharacter:  Comment char, used to ignore comment content,
266#                           default is DataType.TAB_COMMENT_SPLIT
267#
268def CleanString(Line, CommentCharacter=DataType.TAB_COMMENT_SPLIT, AllowCppStyleComment=False):
269    #
270    # remove whitespace
271    #
272    Line = Line.strip()
273    #
274    # Replace EDK1's comment character
275    #
276    if AllowCppStyleComment:
277        Line = Line.replace(DataType.TAB_COMMENT_EDK1_SPLIT, CommentCharacter)
278    #
279    # remove comments, but we should escape comment character in string
280    #
281    InString = False
282    for Index in range(0, len(Line)):
283        if Line[Index] == '"':
284            InString = not InString
285        elif Line[Index] == CommentCharacter and not InString:
286            Line = Line[0: Index]
287            break
288    #
289    # remove whitespace again
290    #
291    Line = Line.strip()
292
293    return Line
294
295## CleanString2
296#
297# Split comments in a string
298# Remove spaces
299#
300# @param Line:              The string to be cleaned
301# @param CommentCharacter:  Comment char, used to ignore comment content,
302#                           default is DataType.TAB_COMMENT_SPLIT
303#
304def CleanString2(Line, CommentCharacter=DataType.TAB_COMMENT_SPLIT, AllowCppStyleComment=False):
305    #
306    # remove whitespace
307    #
308    Line = Line.strip()
309    #
310    # Replace EDK1's comment character
311    #
312    if AllowCppStyleComment:
313        Line = Line.replace(DataType.TAB_COMMENT_EDK1_SPLIT, CommentCharacter)
314    #
315    # separate comments and statements
316    #
317    LineParts = Line.split(CommentCharacter, 1)
318    #
319    # remove whitespace again
320    #
321    Line = LineParts[0].strip()
322    if len(LineParts) > 1:
323        Comment = LineParts[1].strip()
324        #
325        # Remove prefixed and trailing comment characters
326        #
327        Start = 0
328        End = len(Comment)
329        while Start < End and Comment.startswith(CommentCharacter, Start, End):
330            Start += 1
331        while End >= 0 and Comment.endswith(CommentCharacter, Start, End):
332            End -= 1
333        Comment = Comment[Start:End]
334        Comment = Comment.strip()
335    else:
336        Comment = ''
337
338    return Line, Comment
339
340## GetMultipleValuesOfKeyFromLines
341#
342# Parse multiple strings to clean comment and spaces
343# The result is saved to KeyValues
344#
345# @param Lines:             The content to be parsed
346# @param Key:               Reserved
347# @param KeyValues:         To store data after parsing
348# @param CommentCharacter:  Comment char, used to ignore comment content
349#
350def GetMultipleValuesOfKeyFromLines(Lines, Key, KeyValues, CommentCharacter):
351    if Key:
352        pass
353    if KeyValues:
354        pass
355    Lines = Lines.split(DataType.TAB_SECTION_END, 1)[1]
356    LineList = Lines.split('\n')
357    for Line in LineList:
358        Line = CleanString(Line, CommentCharacter)
359        if Line != '' and Line[0] != CommentCharacter:
360            KeyValues += [Line]
361    return True
362
363## GetDefineValue
364#
365# Parse a DEFINE statement to get defined value
366# DEFINE Key Value
367#
368# @param String:            The content to be parsed
369# @param Key:               The key of DEFINE statement
370# @param CommentCharacter:  Comment char, used to ignore comment content
371#
372def GetDefineValue(String, Key, CommentCharacter):
373    if CommentCharacter:
374        pass
375    String = CleanString(String)
376    return String[String.find(Key + ' ') + len(Key + ' ') : ]
377
378## GetSingleValueOfKeyFromLines
379#
380# Parse multiple strings as below to get value of each definition line
381# Key1 = Value1
382# Key2 = Value2
383# The result is saved to Dictionary
384#
385# @param Lines:                The content to be parsed
386# @param Dictionary:           To store data after parsing
387# @param CommentCharacter:     Comment char, be used to ignore comment content
388# @param KeySplitCharacter:    Key split char, between key name and key value.
389#                              Key1 = Value1, '=' is the key split char
390# @param ValueSplitFlag:       Value split flag, be used to decide if has
391#                              multiple values
392# @param ValueSplitCharacter:  Value split char, be used to split multiple
393#                              values. Key1 = Value1|Value2, '|' is the value
394#                              split char
395#
396def GetSingleValueOfKeyFromLines(Lines, Dictionary, CommentCharacter, KeySplitCharacter, \
397                                 ValueSplitFlag, ValueSplitCharacter):
398    Lines = Lines.split('\n')
399    Keys = []
400    Value = ''
401    DefineValues = ['']
402    SpecValues = ['']
403
404    for Line in Lines:
405        #
406        # Handle DEFINE and SPEC
407        #
408        if Line.find(DataType.TAB_INF_DEFINES_DEFINE + ' ') > -1:
409            if '' in DefineValues:
410                DefineValues.remove('')
411            DefineValues.append(GetDefineValue(Line, DataType.TAB_INF_DEFINES_DEFINE, CommentCharacter))
412            continue
413        if Line.find(DataType.TAB_INF_DEFINES_SPEC + ' ') > -1:
414            if '' in SpecValues:
415                SpecValues.remove('')
416            SpecValues.append(GetDefineValue(Line, DataType.TAB_INF_DEFINES_SPEC, CommentCharacter))
417            continue
418
419        #
420        # Handle Others
421        #
422        LineList = Line.split(KeySplitCharacter, 1)
423        if len(LineList) >= 2:
424            Key = LineList[0].split()
425            if len(Key) == 1 and Key[0][0] != CommentCharacter:
426                #
427                # Remove comments and white spaces
428                #
429                LineList[1] = CleanString(LineList[1], CommentCharacter)
430                if ValueSplitFlag:
431                    Value = list(map(lambda x: x.strip(), LineList[1].split(ValueSplitCharacter)))
432                else:
433                    Value = CleanString(LineList[1], CommentCharacter).splitlines()
434
435                if Key[0] in Dictionary:
436                    if Key[0] not in Keys:
437                        Dictionary[Key[0]] = Value
438                        Keys.append(Key[0])
439                    else:
440                        Dictionary[Key[0]].extend(Value)
441                else:
442                    Dictionary[DataType.TAB_INF_DEFINES_MACRO][Key[0]] = Value[0]
443
444    if DefineValues == []:
445        DefineValues = ['']
446    if SpecValues == []:
447        SpecValues = ['']
448    Dictionary[DataType.TAB_INF_DEFINES_DEFINE] = DefineValues
449    Dictionary[DataType.TAB_INF_DEFINES_SPEC] = SpecValues
450
451    return True
452
453## The content to be parsed
454#
455# Do pre-check for a file before it is parsed
456# Check $()
457# Check []
458#
459# @param FileName:       Used for error report
460# @param FileContent:    File content to be parsed
461# @param SupSectionTag:  Used for error report
462#
463def PreCheck(FileName, FileContent, SupSectionTag):
464    if SupSectionTag:
465        pass
466    LineNo = 0
467    IsFailed = False
468    NewFileContent = ''
469    for Line in FileContent.splitlines():
470        LineNo = LineNo + 1
471        #
472        # Clean current line
473        #
474        Line = CleanString(Line)
475        #
476        # Remove commented line
477        #
478        if Line.find(DataType.TAB_COMMA_SPLIT) == 0:
479            Line = ''
480        #
481        # Check $()
482        #
483        if Line.find('$') > -1:
484            if Line.find('$(') < 0 or Line.find(')') < 0:
485                Logger.Error("Parser", FORMAT_INVALID, Line=LineNo, File=FileName, RaiseError=Logger.IS_RAISE_ERROR)
486        #
487        # Check []
488        #
489        if Line.find('[') > -1 or Line.find(']') > -1:
490            #
491            # Only get one '[' or one ']'
492            #
493            if not (Line.find('[') > -1 and Line.find(']') > -1):
494                Logger.Error("Parser", FORMAT_INVALID, Line=LineNo, File=FileName, RaiseError=Logger.IS_RAISE_ERROR)
495        #
496        # Regenerate FileContent
497        #
498        NewFileContent = NewFileContent + Line + '\r\n'
499
500    if IsFailed:
501        Logger.Error("Parser", FORMAT_INVALID, Line=LineNo, File=FileName, RaiseError=Logger.IS_RAISE_ERROR)
502
503    return NewFileContent
504
505## CheckFileType
506#
507# Check if the Filename is including ExtName
508# Return True if it exists
509# Raise a error message if it not exists
510#
511# @param CheckFilename:      Name of the file to be checked
512# @param ExtName:            Ext name of the file to be checked
513# @param ContainerFilename:  The container file which describes the file to be
514#                            checked, used for error report
515# @param SectionName:        Used for error report
516# @param Line:               The line in container file which defines the file
517#                            to be checked
518#
519def CheckFileType(CheckFilename, ExtName, ContainerFilename, SectionName, Line, LineNo= -1):
520    if CheckFilename != '' and CheckFilename is not None:
521        (Root, Ext) = os.path.splitext(CheckFilename)
522        if Ext.upper() != ExtName.upper() and Root:
523            ContainerFile = open(ContainerFilename, 'r').read()
524            if LineNo == -1:
525                LineNo = GetLineNo(ContainerFile, Line)
526            ErrorMsg = ST.ERR_SECTIONNAME_INVALID % (SectionName, CheckFilename, ExtName)
527            Logger.Error("Parser", PARSER_ERROR, ErrorMsg, Line=LineNo, \
528                         File=ContainerFilename, RaiseError=Logger.IS_RAISE_ERROR)
529
530    return True
531
532## CheckFileExist
533#
534# Check if the file exists
535# Return True if it exists
536# Raise a error message if it not exists
537#
538# @param CheckFilename:      Name of the file to be checked
539# @param WorkspaceDir:       Current workspace dir
540# @param ContainerFilename:  The container file which describes the file to
541#                            be checked, used for error report
542# @param SectionName:        Used for error report
543# @param Line:               The line in container file which defines the
544#                            file to be checked
545#
546def CheckFileExist(WorkspaceDir, CheckFilename, ContainerFilename, SectionName, Line, LineNo= -1):
547    CheckFile = ''
548    if CheckFilename != '' and CheckFilename is not None:
549        CheckFile = WorkspaceFile(WorkspaceDir, CheckFilename)
550        if not os.path.isfile(CheckFile):
551            ContainerFile = open(ContainerFilename, 'r').read()
552            if LineNo == -1:
553                LineNo = GetLineNo(ContainerFile, Line)
554            ErrorMsg = ST.ERR_CHECKFILE_NOTFOUND % (CheckFile, SectionName)
555            Logger.Error("Parser", PARSER_ERROR, ErrorMsg,
556                            File=ContainerFilename, Line=LineNo, RaiseError=Logger.IS_RAISE_ERROR)
557    return CheckFile
558
559## GetLineNo
560#
561# Find the index of a line in a file
562#
563# @param FileContent:  Search scope
564# @param Line:         Search key
565#
566def GetLineNo(FileContent, Line, IsIgnoreComment=True):
567    LineList = FileContent.splitlines()
568    for Index in range(len(LineList)):
569        if LineList[Index].find(Line) > -1:
570            #
571            # Ignore statement in comment
572            #
573            if IsIgnoreComment:
574                if LineList[Index].strip()[0] == DataType.TAB_COMMENT_SPLIT:
575                    continue
576            return Index + 1
577
578    return -1
579
580## RaiseParserError
581#
582# Raise a parser error
583#
584# @param Line:     String which has error
585# @param Section:  Used for error report
586# @param File:     File which has the string
587# @param Format:   Correct format
588#
589def RaiseParserError(Line, Section, File, Format='', LineNo= -1):
590    if LineNo == -1:
591        LineNo = GetLineNo(open(os.path.normpath(File), 'r').read(), Line)
592    ErrorMsg = ST.ERR_INVALID_NOTFOUND % (Line, Section)
593    if Format != '':
594        Format = "Correct format is " + Format
595    Logger.Error("Parser", PARSER_ERROR, ErrorMsg, File=File, Line=LineNo, \
596                 ExtraData=Format, RaiseError=Logger.IS_RAISE_ERROR)
597
598## WorkspaceFile
599#
600# Return a full path with workspace dir
601#
602# @param WorkspaceDir:  Workspace dir
603# @param Filename:      Relative file name
604#
605def WorkspaceFile(WorkspaceDir, Filename):
606    return os.path.join(NormPath(WorkspaceDir), NormPath(Filename))
607
608## Split string
609#
610# Remove '"' which startswith and endswith string
611#
612# @param String:  The string need to be split
613#
614def SplitString(String):
615    if String.startswith('\"'):
616        String = String[1:]
617    if String.endswith('\"'):
618        String = String[:-1]
619    return String
620
621## Convert To Sql String
622#
623# Replace "'" with "''" in each item of StringList
624#
625# @param StringList:  A list for strings to be converted
626#
627def ConvertToSqlString(StringList):
628    return list(map(lambda s: s.replace("'", "''"), StringList))
629
630## Convert To Sql String
631#
632# Replace "'" with "''" in the String
633#
634# @param String:  A String to be converted
635#
636def ConvertToSqlString2(String):
637    return String.replace("'", "''")
638
639## GetStringOfList
640#
641# Get String of a List
642#
643# @param Lines: string list
644# @param Split: split character
645#
646def GetStringOfList(List, Split=' '):
647    if not isinstance(List, type([])):
648        return List
649    Str = ''
650    for Item in List:
651        Str = Str + Item + Split
652    return Str.strip()
653
654## Get HelpTextList
655#
656# Get HelpTextList from HelpTextClassList
657#
658# @param HelpTextClassList: Help Text Class List
659#
660def GetHelpTextList(HelpTextClassList):
661    List = []
662    if HelpTextClassList:
663        for HelpText in HelpTextClassList:
664            if HelpText.String.endswith('\n'):
665                HelpText.String = HelpText.String[0: len(HelpText.String) - len('\n')]
666                List.extend(HelpText.String.split('\n'))
667    return List
668
669## Get String Array Length
670#
671# Get String Array Length
672#
673# @param String: the source string
674#
675def StringArrayLength(String):
676    if String.startswith('L"'):
677        return (len(String) - 3 + 1) * 2
678    elif String.startswith('"'):
679        return (len(String) - 2 + 1)
680    else:
681        return len(String.split()) + 1
682
683## RemoveDupOption
684#
685# Remove Dup Option
686#
687# @param OptionString: the option string
688# @param Which: Which flag
689# @param Against: Against flag
690#
691def RemoveDupOption(OptionString, Which="/I", Against=None):
692    OptionList = OptionString.split()
693    ValueList = []
694    if Against:
695        ValueList += Against
696    for Index in range(len(OptionList)):
697        Opt = OptionList[Index]
698        if not Opt.startswith(Which):
699            continue
700        if len(Opt) > len(Which):
701            Val = Opt[len(Which):]
702        else:
703            Val = ""
704        if Val in ValueList:
705            OptionList[Index] = ""
706        else:
707            ValueList.append(Val)
708    return " ".join(OptionList)
709
710## Check if the string is HexDgit
711#
712# Return true if all characters in the string are digits and there is at
713# least one character
714# or valid Hexs (started with 0x, following by hexdigit letters)
715# , false otherwise.
716# @param string: input string
717#
718def IsHexDigit(Str):
719    try:
720        int(Str, 10)
721        return True
722    except ValueError:
723        if len(Str) > 2 and Str.upper().startswith('0X'):
724            try:
725                int(Str, 16)
726                return True
727            except ValueError:
728                return False
729    return False
730
731## Check if the string is HexDgit and its integer value within limit of UINT32
732#
733# Return true if all characters in the string are digits and there is at
734# least one character
735# or valid Hexs (started with 0x, following by hexdigit letters)
736# , false otherwise.
737# @param string: input string
738#
739def IsHexDigitUINT32(Str):
740    try:
741        Value = int(Str, 10)
742        if (Value <= 0xFFFFFFFF) and (Value >= 0):
743            return True
744    except ValueError:
745        if len(Str) > 2 and Str.upper().startswith('0X'):
746            try:
747                Value = int(Str, 16)
748                if (Value <= 0xFFFFFFFF) and (Value >= 0):
749                    return True
750            except ValueError:
751                return False
752    return False
753
754## CleanSpecialChar
755#
756# The ASCII text files of type INF, DEC, INI are edited by developers,
757# and may contain characters that cannot be directly translated to strings that
758# are conformant with the UDP XML Schema.  Any characters in this category
759# (0x00-0x08, TAB [0x09], 0x0B, 0x0C, 0x0E-0x1F, 0x80-0xFF)
760# must be converted to a space character[0x20] as part of the parsing process.
761#
762def ConvertSpecialChar(Lines):
763    RetLines = []
764    for line in Lines:
765        ReMatchSpecialChar = re.compile(r"[\x00-\x08]|\x09|\x0b|\x0c|[\x0e-\x1f]|[\x7f-\xff]")
766        RetLines.append(ReMatchSpecialChar.sub(' ', line))
767
768    return RetLines
769
770## __GetTokenList
771#
772# Assume Str is a valid feature flag expression.
773# Return a list which contains tokens: alpha numeric token and other token
774# Whitespace are not stripped
775#
776def __GetTokenList(Str):
777    InQuote = False
778    Token = ''
779    TokenOP = ''
780    PreChar = ''
781    List = []
782    for Char in Str:
783        if InQuote:
784            Token += Char
785            if Char == '"' and PreChar != '\\':
786                InQuote = not InQuote
787                List.append(Token)
788                Token = ''
789            continue
790        if Char == '"':
791            if Token and Token != 'L':
792                List.append(Token)
793                Token = ''
794            if TokenOP:
795                List.append(TokenOP)
796                TokenOP = ''
797            InQuote = not InQuote
798            Token += Char
799            continue
800
801        if not (Char.isalnum() or Char in '_'):
802            TokenOP += Char
803            if Token:
804                List.append(Token)
805                Token = ''
806        else:
807            Token += Char
808            if TokenOP:
809                List.append(TokenOP)
810                TokenOP = ''
811
812        if PreChar == '\\' and Char == '\\':
813            PreChar = ''
814        else:
815            PreChar = Char
816    if Token:
817        List.append(Token)
818    if TokenOP:
819        List.append(TokenOP)
820    return List
821
822## ConvertNEToNOTEQ
823#
824# Convert NE operator to NOT EQ
825# For example: 1 NE 2 -> 1 NOT EQ 2
826#
827# @param Expr: Feature flag expression to be converted
828#
829def ConvertNEToNOTEQ(Expr):
830    List = __GetTokenList(Expr)
831    for Index in range(len(List)):
832        if List[Index] == 'NE':
833            List[Index] = 'NOT EQ'
834    return ''.join(List)
835
836## ConvertNOTEQToNE
837#
838# Convert NOT EQ operator to NE
839# For example: 1 NOT NE 2 -> 1 NE 2
840#
841# @param Expr: Feature flag expression to be converted
842#
843def ConvertNOTEQToNE(Expr):
844    List = __GetTokenList(Expr)
845    HasNOT = False
846    RetList = []
847    for Token in List:
848        if HasNOT and Token == 'EQ':
849            # At least, 'NOT' is in the list
850            while not RetList[-1].strip():
851                RetList.pop()
852            RetList[-1] = 'NE'
853            HasNOT = False
854            continue
855        if Token == 'NOT':
856            HasNOT = True
857        elif Token.strip():
858            HasNOT = False
859        RetList.append(Token)
860
861    return ''.join(RetList)
862
863## SplitPcdEntry
864#
865# Split an PCD entry string to Token.CName and PCD value and FFE.
866# NOTE: PCD Value and FFE can contain "|" in it's expression. And in INF specification, have below rule.
867# When using the characters "|" or "||" in an expression, the expression must be encapsulated in
868# open "(" and close ")" parenthesis.
869#
870# @param String    An PCD entry string need to be split.
871#
872# @return List     [PcdTokenCName, Value, FFE]
873#
874def SplitPcdEntry(String):
875    if not String:
876        return ['', '', ''], False
877
878    PcdTokenCName = ''
879    PcdValue = ''
880    PcdFeatureFlagExp = ''
881
882    ValueList = GetSplitValueList(String, "|", 1)
883
884    #
885    # Only contain TokenCName
886    #
887    if len(ValueList) == 1:
888        return [ValueList[0]], True
889
890    NewValueList = []
891
892    if len(ValueList) == 2:
893        PcdTokenCName = ValueList[0]
894
895        InQuote = False
896        InParenthesis = False
897        StrItem = ''
898        for StrCh in ValueList[1]:
899            if StrCh == '"':
900                InQuote = not InQuote
901            elif StrCh == '(' or StrCh == ')':
902                InParenthesis = not InParenthesis
903
904            if StrCh == '|':
905                if not InQuote or not InParenthesis:
906                    NewValueList.append(StrItem.strip())
907                    StrItem = ' '
908                    continue
909
910            StrItem += StrCh
911
912        NewValueList.append(StrItem.strip())
913
914        if len(NewValueList) == 1:
915            PcdValue = NewValueList[0]
916            return [PcdTokenCName, PcdValue], True
917        elif len(NewValueList) == 2:
918            PcdValue = NewValueList[0]
919            PcdFeatureFlagExp = NewValueList[1]
920            return [PcdTokenCName, PcdValue, PcdFeatureFlagExp], True
921        else:
922            return ['', '', ''], False
923
924    return ['', '', ''], False
925
926## Check if two arches matched?
927#
928# @param Arch1
929# @param Arch2
930#
931def IsMatchArch(Arch1, Arch2):
932    if 'COMMON' in Arch1 or 'COMMON' in Arch2:
933        return True
934    try:
935        if isinstance(Arch1, list) and isinstance(Arch2, list):
936            for Item1 in Arch1:
937                for Item2 in Arch2:
938                    if Item1 == Item2:
939                        return True
940
941        elif isinstance(Arch1, list):
942            return Arch2 in Arch1
943
944        elif isinstance(Arch2, list):
945            return Arch1 in Arch2
946
947        else:
948            if Arch1 == Arch2:
949                return True
950    except:
951        return False
952
953# Search all files in FilePath to find the FileName with the largest index
954# Return the FileName with index +1 under the FilePath
955#
956def GetUniFileName(FilePath, FileName):
957    Files = []
958    try:
959        Files = os.listdir(FilePath)
960    except:
961        pass
962
963    LargestIndex = -1
964    IndexNotFound = True
965    for File in Files:
966        if File.upper().startswith(FileName.upper()) and File.upper().endswith('.UNI'):
967            Index = File.upper().replace(FileName.upper(), '').replace('.UNI', '')
968            if Index:
969                try:
970                    Index = int(Index)
971                except Exception:
972                    Index = -1
973            else:
974                IndexNotFound = False
975                Index = 0
976            if Index > LargestIndex:
977                LargestIndex = Index + 1
978
979    if LargestIndex > -1 and not IndexNotFound:
980        return os.path.normpath(os.path.join(FilePath, FileName + str(LargestIndex) + '.uni'))
981    else:
982        return os.path.normpath(os.path.join(FilePath, FileName + '.uni'))
983