1## @file
2# This file is for converting package information data file to xml file.
3#
4# Copyright (c) 2011 - 2014, Intel Corporation. All rights reserved.<BR>
5#
6# This program and the accompanying materials are licensed and made available
7# under the terms and conditions of the BSD License which accompanies this
8# distribution. The full text of the license may be found at
9# http://opensource.org/licenses/bsd-license.php
10#
11# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13#
14
15'''
16IniToXml
17'''
18
19import os.path
20import re
21from time import strftime
22from time import localtime
23
24import Logger.Log as Logger
25from Logger.ToolError import UPT_INI_PARSE_ERROR
26from Logger.ToolError import FILE_NOT_FOUND
27from Library.Xml.XmlRoutines import CreateXmlElement
28from Library.DataType import TAB_VALUE_SPLIT
29from Library.DataType import TAB_EQUAL_SPLIT
30from Library.DataType import TAB_SECTION_START
31from Library.DataType import TAB_SECTION_END
32from Logger import StringTable as ST
33from Library.String import ConvertSpecialChar
34from Library.ParserValidate import IsValidPath
35from Library import GlobalData
36
37## log error:
38#
39# @param error: error
40# @param File: File
41# @param Line: Line
42#
43def IniParseError(Error, File, Line):
44    Logger.Error("UPT", UPT_INI_PARSE_ERROR, File=File,
45                 Line=Line, ExtraData=Error)
46
47## __ValidatePath
48#
49# @param Path: Path to be checked
50#
51def __ValidatePath(Path, Root):
52    Path = Path.strip()
53    if os.path.isabs(Path) or not IsValidPath(Path, Root):
54        return False, ST.ERR_FILELIST_LOCATION % (Root, Path)
55    return True, ''
56
57## ValidateMiscFile
58#
59# @param Filename: File to be checked
60#
61def ValidateMiscFile(Filename):
62    Root = GlobalData.gWORKSPACE
63    return __ValidatePath(Filename, Root)
64
65## ValidateToolsFile
66#
67# @param Filename: File to be checked
68#
69def ValidateToolsFile(Filename):
70    Valid, Cause = False, ''
71    if not Valid and 'EDK_TOOLS_PATH' in os.environ:
72        Valid, Cause = __ValidatePath(Filename, os.environ['EDK_TOOLS_PATH'])
73    if not Valid:
74        Valid, Cause = __ValidatePath(Filename, GlobalData.gWORKSPACE)
75    return Valid, Cause
76
77## ParseFileList
78#
79# @param Line: Line
80# @param Map: Map
81# @param CurrentKey: CurrentKey
82# @param PathFunc: Path validate function
83#
84def ParseFileList(Line, Map, CurrentKey, PathFunc):
85    FileList = ["", {}]
86    TokenList = Line.split(TAB_VALUE_SPLIT)
87    if len(TokenList) > 0:
88        Path = TokenList[0].strip().replace('\\', '/')
89        if not Path:
90            return False, ST.ERR_WRONG_FILELIST_FORMAT
91        Valid, Cause = PathFunc(Path)
92        if not Valid:
93            return Valid, Cause
94        FileList[0] = TokenList[0].strip()
95        for Token in TokenList[1:]:
96            Attr = Token.split(TAB_EQUAL_SPLIT)
97            if len(Attr) != 2 or not Attr[0].strip() or not Attr[1].strip():
98                return False, ST.ERR_WRONG_FILELIST_FORMAT
99
100            Key = Attr[0].strip()
101            Val = Attr[1].strip()
102            if Key not in ['OS', 'Executable']:
103                return False, ST.ERR_UNKNOWN_FILELIST_ATTR % Key
104
105            if Key == 'OS' and Val not in ["Win32", "Win64", "Linux32",
106                                           "Linux64", "OS/X32", "OS/X64",
107                                           "GenericWin", "GenericNix"]:
108                return False, ST.ERR_FILELIST_ATTR % 'OS'
109            elif Key == 'Executable' and Val not in ['true', 'false']:
110                return False, ST.ERR_FILELIST_ATTR % 'Executable'
111            FileList[1][Key] = Val
112
113        Map[CurrentKey].append(FileList)
114    return True, ''
115
116## Create header XML file
117#
118# @param DistMap: DistMap
119# @param Root: Root
120#
121def CreateHeaderXml(DistMap, Root):
122    Element1 = CreateXmlElement('Name', DistMap['Name'],
123                                [], [['BaseName', DistMap['BaseName']]])
124    Element2 = CreateXmlElement('GUID', DistMap['GUID'],
125                                [], [['Version', DistMap['Version']]])
126    AttributeList = [['ReadOnly', DistMap['ReadOnly']],
127                     ['RePackage', DistMap['RePackage']]]
128    NodeList = [Element1,
129                Element2,
130                ['Vendor', DistMap['Vendor']],
131                ['Date', DistMap['Date']],
132                ['Copyright', DistMap['Copyright']],
133                ['License', DistMap['License']],
134                ['Abstract', DistMap['Abstract']],
135                ['Description', DistMap['Description']],
136                ['Signature', DistMap['Signature']],
137                ['XmlSpecification', DistMap['XmlSpecification']],
138                ]
139    Root.appendChild(CreateXmlElement('DistributionHeader', '',
140                                      NodeList, AttributeList))
141
142## Create tools XML file
143#
144# @param Map: Map
145# @param Root: Root
146# @param Tag: Tag
147#
148def CreateToolsXml(Map, Root, Tag):
149    #
150    # Check if all elements in this section are empty
151    #
152    for Key in Map:
153        if len(Map[Key]) > 0:
154            break
155    else:
156        return
157
158    NodeList = [['Name', Map['Name']],
159                ['Copyright', Map['Copyright']],
160                ['License', Map['License']],
161                ['Abstract', Map['Abstract']],
162                ['Description', Map['Description']],
163               ]
164    HeaderNode = CreateXmlElement('Header', '', NodeList, [])
165    NodeList = [HeaderNode]
166
167    for File in Map['FileList']:
168        AttrList = []
169        for Key in File[1]:
170            AttrList.append([Key, File[1][Key]])
171        NodeList.append(CreateXmlElement('Filename', File[0], [], AttrList))
172    Root.appendChild(CreateXmlElement(Tag, '', NodeList, []))
173
174## ValidateValues
175#
176# @param Key: Key
177# @param Value: Value
178# @param SectionName: SectionName
179#
180def ValidateValues(Key, Value, SectionName):
181    if SectionName == 'DistributionHeader':
182        Valid, Cause = ValidateRegValues(Key, Value)
183        if not Valid:
184            return Valid, Cause
185        Valid = __ValidateDistHeader(Key, Value)
186        if not Valid:
187            return Valid, ST.ERR_VALUE_INVALID % (Key, SectionName)
188    else:
189        Valid = __ValidateOtherHeader(Key, Value)
190        if not Valid:
191            return Valid, ST.ERR_VALUE_INVALID % (Key, SectionName)
192    return True, ''
193
194## ValidateRegValues
195#
196# @param Key: Key
197# @param Value: Value
198#
199def ValidateRegValues(Key, Value):
200    ValidateMap = {
201        'ReadOnly'  :
202            ('true|false', ST.ERR_BOOLEAN_VALUE % (Key, Value)),
203        'RePackage' :
204            ('true|false', ST.ERR_BOOLEAN_VALUE % (Key, Value)),
205        'GUID'      :
206            ('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}'
207            '-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}',
208            ST.ERR_GUID_VALUE % Value),
209        'Version'   :   ('[0-9]+(\.[0-9]+)?', ST.ERR_VERSION_VALUE % \
210                         (Key, Value)),
211        'XmlSpecification' : ('1\.1', ST.ERR_VERSION_XMLSPEC % Value)
212    }
213    if Key not in ValidateMap:
214        return True, ''
215    Elem = ValidateMap[Key]
216    Match = re.compile(Elem[0]).match(Value)
217    if Match and Match.start() == 0 and Match.end() == len(Value):
218        return True, ''
219    return False, Elem[1]
220
221## __ValidateDistHeaderName
222#
223# @param Name: Name
224#
225def __ValidateDistHeaderName(Name):
226    if len(Name) < 1:
227        return False
228
229    for Char in Name:
230        if ord(Char) < 0x20 or ord(Char) >= 0x7f:
231            return False
232    return True
233
234## __ValidateDistHeaderBaseName
235#
236# @param BaseName: BaseName
237#
238def __ValidateDistHeaderBaseName(BaseName):
239    if not BaseName:
240        return False
241#    if CheckLen and len(BaseName) < 2:
242#        return False
243    if not BaseName[0].isalnum() and BaseName[0] != '_':
244        return False
245    for Char in BaseName[1:]:
246        if not Char.isalnum() and Char not in '-_':
247            return False
248    return True
249
250## __ValidateDistHeaderAbstract
251#
252# @param Abstract: Abstract
253#
254def __ValidateDistHeaderAbstract(Abstract):
255    return '\t' not in Abstract and len(Abstract.splitlines()) == 1
256
257## __ValidateOtherHeaderAbstract
258#
259# @param Abstract: Abstract
260#
261def __ValidateOtherHeaderAbstract(Abstract):
262    return __ValidateDistHeaderAbstract(Abstract)
263
264## __ValidateDistHeader
265#
266# @param Key: Key
267# @param Value: Value
268#
269def __ValidateDistHeader(Key, Value):
270    ValidateMap = {
271        'Name'      : __ValidateDistHeaderName,
272        'BaseName'  : __ValidateDistHeaderBaseName,
273        'Abstract'  : __ValidateDistHeaderAbstract,
274        'Vendor'    : __ValidateDistHeaderAbstract
275    }
276    return not (Value and Key in ValidateMap and not ValidateMap[Key](Value))
277
278## __ValidateOtherHeader
279#
280# @param Key: Key
281# @param Value: Value
282#
283def __ValidateOtherHeader(Key, Value):
284    ValidateMap = {
285        'Name'      : __ValidateDistHeaderName,
286        'Abstract'  : __ValidateOtherHeaderAbstract
287    }
288    return not (Value and Key in ValidateMap and not ValidateMap[Key](Value))
289
290## Convert ini file to xml file
291#
292# @param IniFile
293#
294def IniToXml(IniFile):
295    if not os.path.exists(IniFile):
296        Logger.Error("UPT", FILE_NOT_FOUND, ST.ERR_TEMPLATE_NOTFOUND % IniFile)
297
298    DistMap = {'ReadOnly' : '', 'RePackage' : '', 'Name' : '',
299               'BaseName' : '', 'GUID' : '', 'Version' : '', 'Vendor' : '',
300               'Date' : '', 'Copyright' : '', 'License' : '', 'Abstract' : '',
301               'Description' : '', 'Signature' : '', 'XmlSpecification' : ''
302                }
303
304    ToolsMap = {'Name' : '', 'Copyright' : '', 'License' : '',
305                'Abstract' : '', 'Description' : '', 'FileList' : []}
306    #
307    # Only FileList is a list: [['file1', {}], ['file2', {}], ...]
308    #
309    MiscMap = {'Name' : '', 'Copyright' : '', 'License' : '',
310               'Abstract' : '', 'Description' : '', 'FileList' : []}
311
312    SectionMap = {
313                   'DistributionHeader' : DistMap,
314                   'ToolsHeader' : ToolsMap,
315                   'MiscellaneousFilesHeader' : MiscMap
316                   }
317
318    PathValidator = {
319                'ToolsHeader' : ValidateToolsFile,
320                'MiscellaneousFilesHeader' : ValidateMiscFile
321                }
322
323    ParsedSection = []
324
325    SectionName = ''
326    CurrentKey = ''
327    PreMap = None
328    Map = None
329    FileContent = ConvertSpecialChar(open(IniFile, 'rb').readlines())
330    LastIndex = 0
331    for Index in range(0, len(FileContent)):
332        LastIndex = Index
333        Line = FileContent[Index].strip()
334        if Line == '' or Line.startswith(';'):
335            continue
336        if Line[0] == TAB_SECTION_START and Line[-1] == TAB_SECTION_END:
337            CurrentKey = ''
338            SectionName = Line[1:-1].strip()
339            if SectionName not in SectionMap:
340                IniParseError(ST.ERR_SECTION_NAME_INVALID % SectionName,
341                      IniFile, Index+1)
342
343            if SectionName in ParsedSection:
344                IniParseError(ST.ERR_SECTION_REDEFINE % SectionName,
345                      IniFile, Index+1)
346            else:
347                ParsedSection.append(SectionName)
348
349            Map = SectionMap[SectionName]
350            continue
351        if not Map:
352            IniParseError(ST.ERR_SECTION_NAME_NONE, IniFile, Index+1)
353        TokenList = Line.split(TAB_EQUAL_SPLIT, 1)
354        TempKey = TokenList[0].strip()
355        #
356        # Value spanned multiple or same keyword appears more than one time
357        #
358        if len(TokenList) < 2 or TempKey not in Map:
359            if CurrentKey == '':
360                IniParseError(ST.ERR_KEYWORD_INVALID % TempKey,
361                              IniFile, Index+1)
362            elif CurrentKey == 'FileList':
363                #
364                # Special for FileList
365                #
366                Valid, Cause = ParseFileList(Line, Map, CurrentKey,
367                                             PathValidator[SectionName])
368                if not Valid:
369                    IniParseError(Cause, IniFile, Index+1)
370
371            else:
372                #
373                # Multiple lines for one key such as license
374                # Or if string on the left side of '=' is not a keyword
375                #
376                Map[CurrentKey] = ''.join([Map[CurrentKey], '\n', Line])
377                Valid, Cause = ValidateValues(CurrentKey,
378                                              Map[CurrentKey], SectionName)
379                if not Valid:
380                    IniParseError(Cause, IniFile, Index+1)
381            continue
382
383        if (TokenList[1].strip() == ''):
384            IniParseError(ST.ERR_EMPTY_VALUE, IniFile, Index+1)
385
386        #
387        # A keyword found
388        #
389        CurrentKey = TempKey
390        if Map[CurrentKey]:
391            IniParseError(ST.ERR_KEYWORD_REDEFINE % CurrentKey,
392                          IniFile, Index+1)
393
394        if id(Map) != id(PreMap) and Map['Copyright']:
395            PreMap = Map
396            Copyright = Map['Copyright'].lower()
397            Pos = Copyright.find('copyright')
398            if Pos == -1:
399                IniParseError(ST.ERR_COPYRIGHT_CONTENT, IniFile, Index)
400            if not Copyright[Pos + len('copyright'):].lstrip(' ').startswith('('):
401                IniParseError(ST.ERR_COPYRIGHT_CONTENT, IniFile, Index)
402
403        if CurrentKey == 'FileList':
404            Valid, Cause = ParseFileList(TokenList[1], Map, CurrentKey,
405                                         PathValidator[SectionName])
406            if not Valid:
407                IniParseError(Cause, IniFile, Index+1)
408        else:
409            Map[CurrentKey] = TokenList[1].strip()
410            Valid, Cause = ValidateValues(CurrentKey,
411                                          Map[CurrentKey], SectionName)
412            if not Valid:
413                IniParseError(Cause, IniFile, Index+1)
414
415    if id(Map) != id(PreMap) and Map['Copyright'] and 'copyright' not in Map['Copyright'].lower():
416        IniParseError(ST.ERR_COPYRIGHT_CONTENT, IniFile, LastIndex)
417
418    #
419    # Check mandatory keys
420    #
421    CheckMdtKeys(DistMap, IniFile, LastIndex,
422                 (('ToolsHeader', ToolsMap), ('MiscellaneousFilesHeader', MiscMap))
423                 )
424
425    return CreateXml(DistMap, ToolsMap, MiscMap, IniFile)
426
427
428## CheckMdtKeys
429#
430# @param MdtDistKeys: All mandatory keys
431# @param DistMap: Dist content
432# @param IniFile: Ini file
433# @param LastIndex: Last index of Ini file
434# @param Maps: Tools and Misc section name and map. (('section_name', map),*)
435#
436def CheckMdtKeys(DistMap, IniFile, LastIndex, Maps):
437    MdtDistKeys = ['Name', 'GUID', 'Version', 'Vendor', 'Copyright', 'License', 'Abstract', 'XmlSpecification']
438    for Key in MdtDistKeys:
439        if Key not in DistMap or DistMap[Key] == '':
440            IniParseError(ST.ERR_KEYWORD_MANDATORY % Key, IniFile, LastIndex+1)
441
442    if '.' not in DistMap['Version']:
443        DistMap['Version'] = DistMap['Version'] + '.0'
444
445    DistMap['Date'] = str(strftime("%Y-%m-%dT%H:%M:%S", localtime()))
446
447    #
448    # Check Tools Surface Area according to UPT Spec
449    # <Tools> {0,}
450    #     <Header> ... </Header> {0,1}
451    #     <Filename> ... </Filename> {1,}
452    # </Tools>
453    # <Header>
454    #    <Name> xs:normalizedString </Name> {1}
455    #    <Copyright> xs:string </Copyright> {0,1}
456    #    <License> xs:string </License> {0,1}
457    #    <Abstract> xs:normalizedString </Abstract> {0,1}
458    #    <Description> xs:string </Description> {0,1}
459    # </Header>
460    #
461    for Item in Maps:
462        Map = Item[1]
463        NonEmptyKey = 0
464        for Key in Map:
465            if Map[Key]:
466                NonEmptyKey += 1
467
468        if NonEmptyKey > 0 and not Map['FileList']:
469            IniParseError(ST.ERR_KEYWORD_MANDATORY % (Item[0] + '.FileList'), IniFile, LastIndex+1)
470
471        if NonEmptyKey > 0 and not Map['Name']:
472            IniParseError(ST.ERR_KEYWORD_MANDATORY % (Item[0] + '.Name'), IniFile, LastIndex+1)
473
474## CreateXml
475#
476# @param DistMap:  Dist Content
477# @param ToolsMap: Tools Content
478# @param MiscMap:  Misc Content
479# @param IniFile:  Ini File
480#
481def CreateXml(DistMap, ToolsMap, MiscMap, IniFile):
482    Attrs = [['xmlns', 'http://www.uefi.org/2011/1.1'],
483             ['xmlns:xsi', 'http:/www.w3.org/2001/XMLSchema-instance'],
484            ]
485    Root = CreateXmlElement('DistributionPackage', '', [], Attrs)
486    CreateHeaderXml(DistMap, Root)
487    CreateToolsXml(ToolsMap, Root, 'Tools')
488    CreateToolsXml(MiscMap, Root, 'MiscellaneousFiles')
489
490    FileAndExt = IniFile.rsplit('.', 1)
491    if len(FileAndExt) > 1:
492        FileName = FileAndExt[0] + '.xml'
493    else:
494        FileName = IniFile + '.xml'
495    File = open(FileName, 'w')
496
497    try:
498        File.write(Root.toprettyxml(indent = '  '))
499    finally:
500        File.close()
501    return FileName
502
503