## @file
# This file is used to define common parsing related functions used in parsing INF/DEC/DSC process
#
# Copyright (c) 2008 - 2014, Intel Corporation. All rights reserved.<BR>
# This program and the accompanying materials
# are licensed and made available under the terms and conditions of the BSD License
# which accompanies this distribution.  The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php
#
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#

##
# Import Modules
#
from String import *
from CommonDataClass.DataClass import *
from DataType import *

## ParseDefineMacro
#
# Search whole table to find all defined Macro and replaced them with the real values
#
def ParseDefineMacro2(Table, RecordSets, GlobalMacro):
    Macros = {}
    #
    # Find all DEFINE macros in section [Header] and its section
    #
    SqlCommand = """select Value1, Value2, BelongsToItem, StartLine, Arch from %s
                    where Model = %s
                    and Enabled > -1""" % (Table.Table, MODEL_META_DATA_DEFINE)
    RecordSet = Table.Exec(SqlCommand)
    for Record in RecordSet:
        Macros[Record[0]] = Record[1]

    #
    # Overrided by Global Macros
    #
    for Key in GlobalMacro.keys():
        Macros[Key] = GlobalMacro[Key]

    #
    # Replace the Macros
    #
    for Key in RecordSets.keys():
        if RecordSets[Key] != []:
            for Item in RecordSets[Key]:
                Item[0] = ReplaceMacro(Item[0], Macros)

## ParseDefineMacro
#
# Search whole table to find all defined Macro and replaced them with the real values
#
def ParseDefineMacro(Table, GlobalMacro):
    Macros = {}
    #
    # Find all DEFINE macros
    #
    SqlCommand = """select Value1, Value2, BelongsToItem, StartLine, Arch from %s
                    where Model = %s
                    and Enabled > -1""" % (Table.Table, MODEL_META_DATA_DEFINE)
    RecordSet = Table.Exec(SqlCommand)
    for Record in RecordSet:
#***************************************************************************************************************************************************
#            The follow SqlCommand (expr replace) is not supported in Sqlite 3.3.4 which is used in Python 2.5                                     *
#            Reserved Only                                                                                                                         *
#            SqlCommand = """update %s set Value1 = replace(Value1, '%s', '%s')                                                                    *
#                            where ID in (select ID from %s                                                                                        *
#                                         where Model = %s                                                                                         *
#                                         and Value1 like '%%%s%%'                                                                                 *
#                                         and StartLine > %s                                                                                       *
#                                         and Enabled > -1                                                                                         *
#                                         and Arch = '%s')""" % \                                                                                  *
#                                         (self.TblDsc.Table, Record[0], Record[1], self.TblDsc.Table, Record[2], Record[1], Record[3], Record[4]) *
#***************************************************************************************************************************************************
        Macros[Record[0]] = Record[1]

    #
    # Overrided by Global Macros
    #
    for Key in GlobalMacro.keys():
        Macros[Key] = GlobalMacro[Key]

    #
    # Found all defined macro and replaced
    #
    SqlCommand = """select ID, Value1 from %s
                    where Model != %s
                    and Value1 like '%%$(%%' and Value1 like '%%)%%'
                    and Enabled > -1"""  % (Table.Table, MODEL_META_DATA_DEFINE)
    FoundRecords = Table.Exec(SqlCommand)
    for FoundRecord in FoundRecords:
        NewValue = ReplaceMacro(FoundRecord[1], Macros)
        SqlCommand = """update %s set Value1 = '%s'
                        where ID = %s""" % (Table.Table, ConvertToSqlString2(NewValue), FoundRecord[0])
        Table.Exec(SqlCommand)

##QueryDefinesItem
#
# Search item of section [Defines] by name, return its values
#
# @param Table: The Table to be executed
# @param Name:  The Name of item of section [Defines]
# @param Arch:  The Arch of item of section [Defines]
#
# @retval RecordSet: A list of all matched records
#
def QueryDefinesItem(Table, Name, Arch, BelongsToFile):
    SqlCommand = """select Value2 from %s
                    where Model = %s
                    and Value1 = '%s'
                    and Arch = '%s'
                    and BelongsToFile = %s
                    and Enabled > -1""" % (Table.Table, MODEL_META_DATA_HEADER, ConvertToSqlString2(Name), ConvertToSqlString2(Arch), BelongsToFile)
    RecordSet = Table.Exec(SqlCommand)
    if len(RecordSet) < 1:
        SqlCommand = """select Value2 from %s
                    where Model = %s
                    and Value1 = '%s'
                    and Arch = '%s'
                    and BelongsToFile = %s
                    and Enabled > -1""" % (Table.Table, MODEL_META_DATA_HEADER, ConvertToSqlString2(Name), ConvertToSqlString2(TAB_ARCH_COMMON.upper()), BelongsToFile)
        RecordSet = Table.Exec(SqlCommand)
    if len(RecordSet) == 1:
        if Name == TAB_INF_DEFINES_LIBRARY_CLASS:
            return [RecordSet[0][0]]
        else:
            return GetSplitValueList(RecordSet[0][0])
    elif len(RecordSet) < 1:
        return ['']
    elif len(RecordSet) > 1:
        RetVal = []
        for Record in RecordSet:
            if Name == TAB_INF_DEFINES_LIBRARY_CLASS:
                RetVal.append(Record[0])
            else:
                Items = GetSplitValueList(Record[0])
                for Item in Items:
                    RetVal.append(Item)
        return RetVal

##QueryDefinesItem
#
# Search item of section [Defines] by name, return its values
#
# @param Table: The Table to be executed
# @param Name:  The Name of item of section [Defines]
# @param Arch:  The Arch of item of section [Defines]
#
# @retval RecordSet: A list of all matched records
#
def QueryDefinesItem2(Table, Arch, BelongsToFile):
    SqlCommand = """select Value1, Value2, StartLine from %s
                    where Model = %s
                    and Arch = '%s'
                    and BelongsToFile = %s
                    and Enabled > -1""" % (Table.Table, MODEL_META_DATA_HEADER, ConvertToSqlString2(Arch), BelongsToFile)
    RecordSet = Table.Exec(SqlCommand)
    if len(RecordSet) < 1:
        SqlCommand = """select Value1, Value2, StartLine from %s
                    where Model = %s
                    and Arch = '%s'
                    and BelongsToFile = %s
                    and Enabled > -1""" % (Table.Table, MODEL_META_DATA_HEADER, ConvertToSqlString2(TAB_ARCH_COMMON), BelongsToFile)
        RecordSet = Table.Exec(SqlCommand)

    return RecordSet

##QueryDscItem
#
# Search all dsc item for a specific section
#
# @param Table: The Table to be executed
# @param Model:  The type of section
#
# @retval RecordSet: A list of all matched records
#
def QueryDscItem(Table, Model, BelongsToItem, BelongsToFile):
    SqlCommand = """select Value1, Arch, StartLine, ID, Value2 from %s
                    where Model = %s
                    and BelongsToItem = %s
                    and BelongsToFile = %s
                    and Enabled > -1""" % (Table.Table, Model, BelongsToItem, BelongsToFile)
    return Table.Exec(SqlCommand)

##QueryDecItem
#
# Search all dec item for a specific section
#
# @param Table: The Table to be executed
# @param Model:  The type of section
#
# @retval RecordSet: A list of all matched records
#
def QueryDecItem(Table, Model, BelongsToItem):
    SqlCommand = """select Value1, Arch, StartLine, ID, Value2 from %s
                    where Model = %s
                    and BelongsToItem = %s
                    and Enabled > -1""" % (Table.Table, Model, BelongsToItem)
    return Table.Exec(SqlCommand)

##QueryInfItem
#
# Search all dec item for a specific section
#
# @param Table: The Table to be executed
# @param Model: The type of section
#
# @retval RecordSet: A list of all matched records
#
def QueryInfItem(Table, Model, BelongsToItem):
    SqlCommand = """select Value1, Arch, StartLine, ID, Value2 from %s
                    where Model = %s
                    and BelongsToItem = %s
                    and Enabled > -1""" % (Table.Table, Model, BelongsToItem)
    return Table.Exec(SqlCommand)

## GetBuildOption
#
# Parse a string with format "[<Family>:]<ToolFlag>=Flag"
# Return (Family, ToolFlag, Flag)
#
# @param String:  String with BuildOption statement
# @param File:    The file which defines build option, used in error report
#
# @retval truple() A truple structure as (Family, ToolChain, Flag)
#
def GetBuildOption(String, File, LineNo = -1):
    (Family, ToolChain, Flag) = ('', '', '')
    if String.find(TAB_EQUAL_SPLIT) < 0:
        RaiseParserError(String, 'BuildOptions', File, '[<Family>:]<ToolFlag>=Flag', LineNo)
    else:
        List = GetSplitValueList(String, TAB_EQUAL_SPLIT, MaxSplit = 1)
        if List[0].find(':') > -1:
            Family = List[0][ : List[0].find(':')].strip()
            ToolChain = List[0][List[0].find(':') + 1 : ].strip()
        else:
            ToolChain = List[0].strip()
        Flag = List[1].strip()
    return (Family, ToolChain, Flag)

## Get Library Class
#
# Get Library of Dsc as <LibraryClassKeyWord>|<LibraryInstance>
#
# @param Item:           String as <LibraryClassKeyWord>|<LibraryInstance>
# @param ContainerFile:  The file which describes the library class, used for error report
#
# @retval (LibraryClassKeyWord, LibraryInstance, [SUP_MODULE_LIST]) Formatted Library Item
#
def GetLibraryClass(Item, ContainerFile, WorkspaceDir, LineNo = -1):
    List = GetSplitValueList(Item[0])
    SupMod = SUP_MODULE_LIST_STRING
    if len(List) != 2:
        RaiseParserError(Item[0], 'LibraryClasses', ContainerFile, '<LibraryClassKeyWord>|<LibraryInstance>')
    else:
        CheckFileType(List[1], '.Inf', ContainerFile, 'library class instance', Item[0], LineNo)
        CheckFileExist(WorkspaceDir, List[1], ContainerFile, 'LibraryClasses', Item[0], LineNo)
        if Item[1] != '':
            SupMod = Item[1]

    return (List[0], List[1], SupMod)

## Get Library Class
#
# Get Library of Dsc as <LibraryClassKeyWord>[|<LibraryInstance>][|<TokenSpaceGuidCName>.<PcdCName>]
#
# @param Item:           String as <LibraryClassKeyWord>|<LibraryInstance>
# @param ContainerFile:  The file which describes the library class, used for error report
#
# @retval (LibraryClassKeyWord, LibraryInstance, [SUP_MODULE_LIST]) Formatted Library Item
#
def GetLibraryClassOfInf(Item, ContainerFile, WorkspaceDir, LineNo = -1):
    ItemList = GetSplitValueList((Item[0] + DataType.TAB_VALUE_SPLIT * 2))
    SupMod = SUP_MODULE_LIST_STRING

    if len(ItemList) > 5:
        RaiseParserError(Item[0], 'LibraryClasses', ContainerFile, '<LibraryClassKeyWord>[|<LibraryInstance>][|<TokenSpaceGuidCName>.<PcdCName>]')
    else:
        CheckFileType(ItemList[1], '.Inf', ContainerFile, 'LibraryClasses', Item[0], LineNo)
        CheckFileExist(WorkspaceDir, ItemList[1], ContainerFile, 'LibraryClasses', Item[0], LineNo)
        if ItemList[2] != '':
            CheckPcdTokenInfo(ItemList[2], 'LibraryClasses', ContainerFile, LineNo)
        if Item[1] != '':
            SupMod = Item[1]

    return (ItemList[0], ItemList[1], ItemList[2], SupMod)

## CheckPcdTokenInfo
#
# Check if PcdTokenInfo is following <TokenSpaceGuidCName>.<PcdCName>
#
# @param TokenInfoString:  String to be checked
# @param Section:          Used for error report
# @param File:             Used for error report
#
# @retval True PcdTokenInfo is in correct format
#
def CheckPcdTokenInfo(TokenInfoString, Section, File, LineNo = -1):
    Format = '<TokenSpaceGuidCName>.<PcdCName>'
    if TokenInfoString != '' and TokenInfoString != None:
        TokenInfoList = GetSplitValueList(TokenInfoString, TAB_SPLIT)
        if len(TokenInfoList) == 2:
            return True

    RaiseParserError(TokenInfoString, Section, File, Format, LineNo)

## Get Pcd
#
# Get Pcd of Dsc as <PcdTokenSpaceGuidCName>.<TokenCName>|<Value>[|<Type>|<MaximumDatumSize>]
#
# @param Item:           String as <PcdTokenSpaceGuidCName>.<TokenCName>|<Value>[|<Type>|<MaximumDatumSize>]
# @param ContainerFile:  The file which describes the pcd, used for error report
#
# @retval (TokenInfo[1], TokenInfo[0], List[1], List[2], List[3], Type)
#
def GetPcd(Item, Type, ContainerFile, LineNo = -1):
    TokenGuid, TokenName, Value, MaximumDatumSize, Token = '', '', '', '', ''
    List = GetSplitValueList(Item + TAB_VALUE_SPLIT * 2)

    if len(List) < 4 or len(List) > 6:
        RaiseParserError(Item, 'Pcds' + Type, ContainerFile, '<PcdTokenSpaceGuidCName>.<TokenCName>|<Value>[|<Type>|<MaximumDatumSize>]', LineNo)
    else:
        Value = List[1]
        MaximumDatumSize = List[2]
        Token = List[3]

    if CheckPcdTokenInfo(List[0], 'Pcds' + Type, ContainerFile, LineNo):
        (TokenGuid, TokenName) = GetSplitValueList(List[0], TAB_SPLIT)

    return (TokenName, TokenGuid, Value, MaximumDatumSize, Token, Type)

## Get FeatureFlagPcd
#
# Get FeatureFlagPcd of Dsc as <PcdTokenSpaceGuidCName>.<TokenCName>|TRUE/FALSE
#
# @param Item:           String as <PcdTokenSpaceGuidCName>.<TokenCName>|TRUE/FALSE
# @param ContainerFile:  The file which describes the pcd, used for error report
#
# @retval (TokenInfo[1], TokenInfo[0], List[1], Type)
#
def GetFeatureFlagPcd(Item, Type, ContainerFile, LineNo = -1):
    TokenGuid, TokenName, Value = '', '', ''
    List = GetSplitValueList(Item)
    if len(List) != 2:
        RaiseParserError(Item, 'Pcds' + Type, ContainerFile, '<PcdTokenSpaceGuidCName>.<TokenCName>|TRUE/FALSE', LineNo)
    else:
        Value = List[1]
    if CheckPcdTokenInfo(List[0], 'Pcds' + Type, ContainerFile, LineNo):
        (TokenGuid, TokenName) = GetSplitValueList(List[0], DataType.TAB_SPLIT)

    return (TokenName, TokenGuid, Value, Type)

## Get DynamicDefaultPcd
#
# Get DynamicDefaultPcd of Dsc as <PcdTokenSpaceGuidCName>.<TokenCName>|<Value>[|<DatumTyp>[|<MaxDatumSize>]]
#
# @param Item:           String as <PcdTokenSpaceGuidCName>.<TokenCName>|TRUE/FALSE
# @param ContainerFile:  The file which describes the pcd, used for error report
#
# @retval (TokenInfo[1], TokenInfo[0], List[1], List[2], List[3], Type)
#
def GetDynamicDefaultPcd(Item, Type, ContainerFile, LineNo = -1):
    TokenGuid, TokenName, Value, DatumTyp, MaxDatumSize = '', '', '', '', ''
    List = GetSplitValueList(Item + TAB_VALUE_SPLIT * 2)
    if len(List) < 4 or len(List) > 8:
        RaiseParserError(Item, 'Pcds' + Type, ContainerFile, '<PcdTokenSpaceGuidCName>.<TokenCName>|<Value>[|<DatumTyp>[|<MaxDatumSize>]]', LineNo)
    else:
        Value = List[1]
        DatumTyp = List[2]
        MaxDatumSize = List[3]
    if CheckPcdTokenInfo(List[0], 'Pcds' + Type, ContainerFile, LineNo):
        (TokenGuid, TokenName) = GetSplitValueList(List[0], TAB_SPLIT)

    return (TokenName, TokenGuid, Value, DatumTyp, MaxDatumSize, Type)

## Get DynamicHiiPcd
#
# Get DynamicHiiPcd of Dsc as <PcdTokenSpaceGuidCName>.<TokenCName>|<String>|<VariableGuidCName>|<VariableOffset>[|<DefaultValue>[|<MaximumDatumSize>]]
#
# @param Item:           String as <PcdTokenSpaceGuidCName>.<TokenCName>|TRUE/FALSE
# @param ContainerFile:  The file which describes the pcd, used for error report
#
# @retval (TokenInfo[1], TokenInfo[0], List[1], List[2], List[3], List[4], List[5], Type)
#
def GetDynamicHiiPcd(Item, Type, ContainerFile, LineNo = -1):
    TokenGuid, TokenName, L1, L2, L3, L4, L5 = '', '', '', '', '', '', ''
    List = GetSplitValueList(Item + TAB_VALUE_SPLIT * 2)
    if len(List) < 6 or len(List) > 8:
        RaiseParserError(Item, 'Pcds' + Type, ContainerFile, '<PcdTokenSpaceGuidCName>.<TokenCName>|<String>|<VariableGuidCName>|<VariableOffset>[|<DefaultValue>[|<MaximumDatumSize>]]', LineNo)
    else:
        L1, L2, L3, L4, L5 = List[1], List[2], List[3], List[4], List[5]
    if CheckPcdTokenInfo(List[0], 'Pcds' + Type, ContainerFile, LineNo):
        (TokenGuid, TokenName) = GetSplitValueList(List[0], DataType.TAB_SPLIT)

    return (TokenName, TokenGuid, L1, L2, L3, L4, L5, Type)

## Get DynamicVpdPcd
#
# Get DynamicVpdPcd of Dsc as <PcdTokenSpaceGuidCName>.<TokenCName>|<VpdOffset>[|<MaximumDatumSize>]
#
# @param Item:           String as <PcdTokenSpaceGuidCName>.<TokenCName>|TRUE/FALSE
# @param ContainerFile:  The file which describes the pcd, used for error report
#
# @retval (TokenInfo[1], TokenInfo[0], List[1], List[2], Type)
#
def GetDynamicVpdPcd(Item, Type, ContainerFile, LineNo = -1):
    TokenGuid, TokenName, L1, L2 = '', '', '', ''
    List = GetSplitValueList(Item + TAB_VALUE_SPLIT)
    if len(List) < 3 or len(List) > 4:
        RaiseParserError(Item, 'Pcds' + Type, ContainerFile, '<PcdTokenSpaceGuidCName>.<TokenCName>|<VpdOffset>[|<MaximumDatumSize>]', LineNo)
    else:
        L1, L2 = List[1], List[2]
    if CheckPcdTokenInfo(List[0], 'Pcds' + Type, ContainerFile, LineNo):
        (TokenGuid, TokenName) = GetSplitValueList(List[0], DataType.TAB_SPLIT)

    return (TokenName, TokenGuid, L1, L2, Type)

## GetComponent
#
# Parse block of the components defined in dsc file
# Set KeyValues as [ ['component name', [lib1, lib2, lib3], [bo1, bo2, bo3], [pcd1, pcd2, pcd3]], ...]
#
# @param Lines:             The content to be parsed
# @param KeyValues:         To store data after parsing
#
# @retval True Get component successfully
#
def GetComponent(Lines, KeyValues):
    (findBlock, findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, False, False, False, False)
    ListItem = None
    LibraryClassItem = []
    BuildOption = []
    Pcd = []

    for Line in Lines:
        Line = Line[0]

        #
        # Ignore !include statement
        #
        if Line.upper().find(TAB_INCLUDE.upper() + ' ') > -1 or Line.upper().find(TAB_DEFINE + ' ') > -1:
            continue

        if findBlock == False:
            ListItem = Line
            #
            # find '{' at line tail
            #
            if Line.endswith('{'):
                findBlock = True
                ListItem = CleanString(Line.rsplit('{', 1)[0], DataType.TAB_COMMENT_SPLIT)

        #
        # Parse a block content
        #
        if findBlock:
            if Line.find('<LibraryClasses>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (True, False, False, False, False, False, False)
                continue
            if Line.find('<BuildOptions>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, True, False, False, False, False, False)
                continue
            if Line.find('<PcdsFeatureFlag>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, True, False, False, False, False)
                continue
            if Line.find('<PcdsPatchableInModule>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, True, False, False, False)
                continue
            if Line.find('<PcdsFixedAtBuild>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, True, False, False)
                continue
            if Line.find('<PcdsDynamic>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, False, True, False)
                continue
            if Line.find('<PcdsDynamicEx>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, False, False, True)
                continue
            if Line.endswith('}'):
                #
                # find '}' at line tail
                #
                KeyValues.append([ListItem, LibraryClassItem, BuildOption, Pcd])
                (findBlock, findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, False, False, False, False)
                LibraryClassItem, BuildOption, Pcd = [], [], []
                continue

        if findBlock:
            if findLibraryClass:
                LibraryClassItem.append(Line)
            elif findBuildOption:
                BuildOption.append(Line)
            elif findPcdsFeatureFlag:
                Pcd.append((DataType.TAB_PCDS_FEATURE_FLAG_NULL, Line))
            elif findPcdsPatchableInModule:
                Pcd.append((DataType.TAB_PCDS_PATCHABLE_IN_MODULE_NULL, Line))
            elif findPcdsFixedAtBuild:
                Pcd.append((DataType.TAB_PCDS_FIXED_AT_BUILD_NULL, Line))
            elif findPcdsDynamic:
                Pcd.append((DataType.TAB_PCDS_DYNAMIC_DEFAULT_NULL, Line))
            elif findPcdsDynamicEx:
                Pcd.append((DataType.TAB_PCDS_DYNAMIC_EX_DEFAULT_NULL, Line))
        else:
            KeyValues.append([ListItem, [], [], []])

    return True

## GetExec
#
# Parse a string with format "InfFilename [EXEC = ExecFilename]"
# Return (InfFilename, ExecFilename)
#
# @param String:  String with EXEC statement
#
# @retval truple() A pair as (InfFilename, ExecFilename)
#
def GetExec(String):
    InfFilename = ''
    ExecFilename = ''
    if String.find('EXEC') > -1:
        InfFilename = String[ : String.find('EXEC')].strip()
        ExecFilename = String[String.find('EXEC') + len('EXEC') : ].strip()
    else:
        InfFilename = String.strip()

    return (InfFilename, ExecFilename)

## GetComponents
#
# Parse block of the components defined in dsc file
# Set KeyValues as [ ['component name', [lib1, lib2, lib3], [bo1, bo2, bo3], [pcd1, pcd2, pcd3]], ...]
#
# @param Lines:             The content to be parsed
# @param Key:               Reserved
# @param KeyValues:         To store data after parsing
# @param CommentCharacter:  Comment char, used to ignore comment content
#
# @retval True Get component successfully
#
def GetComponents(Lines, Key, KeyValues, CommentCharacter):
    if Lines.find(DataType.TAB_SECTION_END) > -1:
        Lines = Lines.split(DataType.TAB_SECTION_END, 1)[1]
    (findBlock, findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, False, False, False, False)
    ListItem = None
    LibraryClassItem = []
    BuildOption = []
    Pcd = []

    LineList = Lines.split('\n')
    for Line in LineList:
        Line = CleanString(Line, CommentCharacter)
        if Line == None or Line == '':
            continue

        if findBlock == False:
            ListItem = Line
            #
            # find '{' at line tail
            #
            if Line.endswith('{'):
                findBlock = True
                ListItem = CleanString(Line.rsplit('{', 1)[0], CommentCharacter)

        #
        # Parse a block content
        #
        if findBlock:
            if Line.find('<LibraryClasses>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (True, False, False, False, False, False, False)
                continue
            if Line.find('<BuildOptions>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, True, False, False, False, False, False)
                continue
            if Line.find('<PcdsFeatureFlag>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, True, False, False, False, False)
                continue
            if Line.find('<PcdsPatchableInModule>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, True, False, False, False)
                continue
            if Line.find('<PcdsFixedAtBuild>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, True, False, False)
                continue
            if Line.find('<PcdsDynamic>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, False, True, False)
                continue
            if Line.find('<PcdsDynamicEx>') != -1:
                (findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, False, False, True)
                continue
            if Line.endswith('}'):
                #
                # find '}' at line tail
                #
                KeyValues.append([ListItem, LibraryClassItem, BuildOption, Pcd])
                (findBlock, findLibraryClass, findBuildOption, findPcdsFeatureFlag, findPcdsPatchableInModule, findPcdsFixedAtBuild, findPcdsDynamic, findPcdsDynamicEx) = (False, False, False, False, False, False, False, False)
                LibraryClassItem, BuildOption, Pcd = [], [], []
                continue

        if findBlock:
            if findLibraryClass:
                LibraryClassItem.append(Line)
            elif findBuildOption:
                BuildOption.append(Line)
            elif findPcdsFeatureFlag:
                Pcd.append((DataType.TAB_PCDS_FEATURE_FLAG, Line))
            elif findPcdsPatchableInModule:
                Pcd.append((DataType.TAB_PCDS_PATCHABLE_IN_MODULE, Line))
            elif findPcdsFixedAtBuild:
                Pcd.append((DataType.TAB_PCDS_FIXED_AT_BUILD, Line))
            elif findPcdsDynamic:
                Pcd.append((DataType.TAB_PCDS_DYNAMIC, Line))
            elif findPcdsDynamicEx:
                Pcd.append((DataType.TAB_PCDS_DYNAMIC_EX, Line))
        else:
            KeyValues.append([ListItem, [], [], []])

    return True

## Get Source
#
# Get Source of Inf as <Filename>[|<Family>[|<TagName>[|<ToolCode>[|<PcdFeatureFlag>]]]]
#
# @param Item:           String as <Filename>[|<Family>[|<TagName>[|<ToolCode>[|<PcdFeatureFlag>]]]]
# @param ContainerFile:  The file which describes the library class, used for error report
#
# @retval (List[0], List[1], List[2], List[3], List[4])
#
def GetSource(Item, ContainerFile, FileRelativePath, LineNo = -1):
    ItemNew = Item + DataType.TAB_VALUE_SPLIT * 4
    List = GetSplitValueList(ItemNew)
    if len(List) < 5 or len(List) > 9:
        RaiseParserError(Item, 'Sources', ContainerFile, '<Filename>[|<Family>[|<TagName>[|<ToolCode>[|<PcdFeatureFlag>]]]]', LineNo)
    List[0] = NormPath(List[0])
    CheckFileExist(FileRelativePath, List[0], ContainerFile, 'Sources', Item, LineNo)
    if List[4] != '':
        CheckPcdTokenInfo(List[4], 'Sources', ContainerFile, LineNo)

    return (List[0], List[1], List[2], List[3], List[4])

## Get Binary
#
# Get Binary of Inf as <Filename>[|<Family>[|<TagName>[|<ToolCode>[|<PcdFeatureFlag>]]]]
#
# @param Item:           String as <Filename>[|<Family>[|<TagName>[|<ToolCode>[|<PcdFeatureFlag>]]]]
# @param ContainerFile:  The file which describes the library class, used for error report
#
# @retval (List[0], List[1], List[2], List[3])
# @retval List
#
def GetBinary(Item, ContainerFile, FileRelativePath, LineNo = -1):
    ItemNew = Item + DataType.TAB_VALUE_SPLIT
    List = GetSplitValueList(ItemNew)
    if len(List) != 4 and len(List) != 5:
        RaiseParserError(Item, 'Binaries', ContainerFile, "<FileType>|<Filename>|<Target>[|<TokenSpaceGuidCName>.<PcdCName>]", LineNo)
    else:
        if List[3] != '':
            CheckPcdTokenInfo(List[3], 'Binaries', ContainerFile, LineNo)

    if len(List) == 4:
        return (List[0], List[1], List[2], List[3])
    elif len(List) == 3:
        return (List[0], List[1], List[2], '')
    elif len(List) == 2:
        return (List[0], List[1], '', '')
    elif len(List) == 1:
        return (List[0], '', '', '')

## Get Guids/Protocols/Ppis
#
# Get Guids/Protocols/Ppis of Inf as <GuidCName>[|<PcdFeatureFlag>]
#
# @param Item:           String as <GuidCName>[|<PcdFeatureFlag>]
# @param Type:           Type of parsing string
# @param ContainerFile:  The file which describes the library class, used for error report
#
# @retval (List[0], List[1])
#
def GetGuidsProtocolsPpisOfInf(Item, Type, ContainerFile, LineNo = -1):
    ItemNew = Item + TAB_VALUE_SPLIT
    List = GetSplitValueList(ItemNew)
    if List[1] != '':
        CheckPcdTokenInfo(List[1], Type, ContainerFile, LineNo)

    return (List[0], List[1])

## Get Guids/Protocols/Ppis
#
# Get Guids/Protocols/Ppis of Dec as <GuidCName>=<GuidValue>
#
# @param Item:           String as <GuidCName>=<GuidValue>
# @param Type:           Type of parsing string
# @param ContainerFile:  The file which describes the library class, used for error report
#
# @retval (List[0], List[1])
#
def GetGuidsProtocolsPpisOfDec(Item, Type, ContainerFile, LineNo = -1):
    List = GetSplitValueList(Item, DataType.TAB_EQUAL_SPLIT)
    if len(List) != 2:
        RaiseParserError(Item, Type, ContainerFile, '<CName>=<GuidValue>', LineNo)

    return (List[0], List[1])

## GetPackage
#
# Get Package of Inf as <PackagePath>[|<PcdFeatureFlag>]
#
# @param Item:           String as <PackagePath>[|<PcdFeatureFlag>]
# @param Type:           Type of parsing string
# @param ContainerFile:  The file which describes the library class, used for error report
#
# @retval (List[0], List[1])
#
def GetPackage(Item, ContainerFile, FileRelativePath, LineNo = -1):
    ItemNew = Item + TAB_VALUE_SPLIT
    List = GetSplitValueList(ItemNew)
    CheckFileType(List[0], '.Dec', ContainerFile, 'package', List[0], LineNo)
    CheckFileExist(FileRelativePath, List[0], ContainerFile, 'Packages', List[0], LineNo)

    if List[1] != '':
        CheckPcdTokenInfo(List[1], 'Packages', ContainerFile, LineNo)

    return (List[0], List[1])

## Get Pcd Values of Inf
#
# Get Pcd of Inf as <TokenSpaceGuidCName>.<PcdCName>[|<Value>]
#
# @param Item:  The string describes pcd
# @param Type:  The type of Pcd
# @param File:  The file which describes the pcd, used for error report
#
# @retval (TokenSpcCName, TokenCName, Value, ItemType) Formatted Pcd Item
#
def GetPcdOfInf(Item, Type, File, LineNo):
    Format = '<TokenSpaceGuidCName>.<PcdCName>[|<Value>]'
    TokenGuid, TokenName, Value, InfType = '', '', '', ''

    if Type == TAB_PCDS_FIXED_AT_BUILD:
        InfType = TAB_INF_FIXED_PCD
    elif Type == TAB_PCDS_PATCHABLE_IN_MODULE:
        InfType = TAB_INF_PATCH_PCD
    elif Type == TAB_PCDS_FEATURE_FLAG:
        InfType = TAB_INF_FEATURE_PCD
    elif Type == TAB_PCDS_DYNAMIC_EX:
        InfType = TAB_INF_PCD_EX
    elif Type == TAB_PCDS_DYNAMIC:
        InfType = TAB_INF_PCD
    List = GetSplitValueList(Item + DataType.TAB_VALUE_SPLIT)
    if len(List) < 2 or len(List) > 3:
        RaiseParserError(Item, InfType, File, Format, LineNo)
    else:
        Value = List[1]
    TokenInfo = GetSplitValueList(List[0], DataType.TAB_SPLIT)
    if len(TokenInfo) != 2:
        RaiseParserError(Item, InfType, File, Format, LineNo)
    else:
        TokenGuid = TokenInfo[0]
        TokenName = TokenInfo[1]

    return (TokenGuid, TokenName, Value, Type)


## Get Pcd Values of Dec
#
# Get Pcd of Dec as <TokenSpcCName>.<TokenCName>|<Value>|<DatumType>|<Token>
# @retval (TokenSpcCName, TokenCName, Value, DatumType, Token, ItemType) Formatted Pcd Item
#
def GetPcdOfDec(Item, Type, File, LineNo = -1):
    Format = '<TokenSpaceGuidCName>.<PcdCName>|<Value>|<DatumType>|<Token>'
    TokenGuid, TokenName, Value, DatumType, Token = '', '', '', '', ''
    List = GetSplitValueList(Item)
    if len(List) != 4:
        RaiseParserError(Item, 'Pcds' + Type, File, Format, LineNo)
    else:
        Value = List[1]
        DatumType = List[2]
        Token = List[3]
    TokenInfo = GetSplitValueList(List[0], DataType.TAB_SPLIT)
    if len(TokenInfo) != 2:
        RaiseParserError(Item, 'Pcds' + Type, File, Format, LineNo)
    else:
        TokenGuid = TokenInfo[0]
        TokenName = TokenInfo[1]

    return (TokenGuid, TokenName, Value, DatumType, Token, Type)

## Parse DEFINE statement
#
# Get DEFINE macros
#
# 1. Insert a record into TblDec
# Value1: Macro Name
# Value2: Macro Value
#
def ParseDefine(LineValue, StartLine, Table, FileID, Filename, SectionName, SectionModel, Arch):
    EdkLogger.debug(EdkLogger.DEBUG_2, "DEFINE statement '%s' found in section %s" % (LineValue, SectionName))
    Define = GetSplitValueList(CleanString(LineValue[LineValue.upper().find(DataType.TAB_DEFINE.upper() + ' ') + len(DataType.TAB_DEFINE + ' ') : ]), TAB_EQUAL_SPLIT, 1)
    Table.Insert(MODEL_META_DATA_DEFINE, Define[0], Define[1], '', '', '', Arch, SectionModel, FileID, StartLine, -1, StartLine, -1, 0)

## InsertSectionItems
#
# Insert item data of a section to a dict
#
def InsertSectionItems(Model, CurrentSection, SectionItemList, ArchList, ThirdList, RecordSet):
    # Insert each item data of a section
    for Index in range(0, len(ArchList)):
        Arch = ArchList[Index]
        Third = ThirdList[Index]
        if Arch == '':
            Arch = TAB_ARCH_COMMON

        Records = RecordSet[Model]
        for SectionItem in SectionItemList:
            BelongsToItem, EndLine, EndColumn = -1, -1, -1
            LineValue, StartLine, EndLine, Comment = SectionItem[0], SectionItem[1], SectionItem[1], SectionItem[2]

            EdkLogger.debug(4, "Parsing %s ..." %LineValue)
            # And then parse DEFINE statement
            if LineValue.upper().find(DataType.TAB_DEFINE.upper() + ' ') > -1:
                continue

            # At last parse other sections
            ID = -1
            Records.append([LineValue, Arch, StartLine, ID, Third, Comment])

        if RecordSet != {}:
            RecordSet[Model] = Records

## Insert records to database
#
# Insert item data of a section to database
# @param Table:            The Table to be inserted
# @param FileID:           The ID of belonging file
# @param Filename:         The name of belonging file
# @param CurrentSection:   The name of currect section
# @param SectionItemList:  A list of items of the section
# @param ArchList:         A list of arches
# @param ThirdList:        A list of third parameters, ModuleType for LibraryClass and SkuId for Dynamic Pcds
# @param IfDefList:        A list of all conditional statements
# @param RecordSet:        A dict of all parsed records
#
def InsertSectionItemsIntoDatabase(Table, FileID, Filename, Model, CurrentSection, SectionItemList, ArchList, ThirdList, IfDefList, RecordSet):
    #
    # Insert each item data of a section
    #
    for Index in range(0, len(ArchList)):
        Arch = ArchList[Index]
        Third = ThirdList[Index]
        if Arch == '':
            Arch = TAB_ARCH_COMMON

        Records = RecordSet[Model]
        for SectionItem in SectionItemList:
            BelongsToItem, EndLine, EndColumn = -1, -1, -1
            LineValue, StartLine, EndLine = SectionItem[0], SectionItem[1], SectionItem[1]

            EdkLogger.debug(4, "Parsing %s ..." %LineValue)
            #
            # And then parse DEFINE statement
            #
            if LineValue.upper().find(DataType.TAB_DEFINE.upper() + ' ') > -1:
                ParseDefine(LineValue, StartLine, Table, FileID, Filename, CurrentSection, Model, Arch)
                continue

            #
            # At last parse other sections
            #
            ID = Table.Insert(Model, LineValue, Third, Third, '', '', Arch, -1, FileID, StartLine, -1, StartLine, -1, 0)
            Records.append([LineValue, Arch, StartLine, ID, Third])

        if RecordSet != {}:
            RecordSet[Model] = Records

## GenMetaDatSectionItem
def GenMetaDatSectionItem(Key, Value, List):
    if Key not in List:
        List[Key] = [Value]
    else:
        List[Key].append(Value)

## IsValidWord
#
# Check whether the word is valid.
# <Word>   ::=  (a-zA-Z0-9_)(a-zA-Z0-9_-){0,} Alphanumeric characters with
#               optional
#               dash "-" and/or underscore "_" characters. No whitespace
#               characters are permitted.
#
# @param Word:  The word string need to be checked.
#
def IsValidWord(Word):
    if not Word:
        return False
    #
    # The first char should be alpha, _ or Digit.
    #
    if not Word[0].isalnum() and \
       not Word[0] == '_' and \
       not Word[0].isdigit():
        return False

    LastChar = ''
    for Char in Word[1:]:
        if (not Char.isalpha()) and \
           (not Char.isdigit()) and \
           Char != '-' and \
           Char != '_' and \
           Char != '.':
            return False
        if Char == '.' and LastChar == '.':
            return False
        LastChar = Char

    return True
