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