OSDN Git Service

tests/docker: replace fedora-i386 with debian-i686
[qmiga/qemu.git] / scripts / xml-preprocess.py
1 #!/usr/bin/env python3
2 #
3 # Copyright (c) 2017-2019 Tony Su
4 # Copyright (c) 2023 Red Hat, Inc.
5 #
6 # SPDX-License-Identifier: MIT
7 #
8 # Adapted from https://github.com/peitaosu/XML-Preprocessor
9 #
10 """This is a XML Preprocessor which can be used to process your XML file before
11 you use it, to process conditional statements, variables, iteration
12 statements, error/warning, execute command, etc.
13
14 ## XML Schema
15
16 ### Include Files
17 ```
18 <?include path/to/file ?>
19 ```
20
21 ### Variables
22 ```
23 $(env.EnvironmentVariable)
24
25 $(sys.SystemVariable)
26
27 $(var.CustomVariable)
28 ```
29
30 ### Conditional Statements
31 ```
32 <?if ?>
33
34 <?ifdef ?>
35
36 <?ifndef ?>
37
38 <?else?>
39
40 <?elseif ?>
41
42 <?endif?>
43 ```
44
45 ### Iteration Statements
46 ```
47 <?foreach VARNAME in 1;2;3?>
48     $(var.VARNAME)
49 <?endforeach?>
50 ```
51
52 ### Errors and Warnings
53 ```
54 <?error "This is error message!" ?>
55
56 <?warning "This is warning message!" ?>
57 ```
58
59 ### Commands
60 ```
61 <? cmd "echo hello world" ?>
62 ```
63 """
64
65 import os
66 import platform
67 import re
68 import subprocess
69 import sys
70 from typing import Optional
71 from xml.dom import minidom
72
73
74 class Preprocessor():
75     """This class holds the XML preprocessing state"""
76
77     def __init__(self):
78         self.sys_vars = {
79             "ARCH": platform.architecture()[0],
80             "SOURCE": os.path.abspath(__file__),
81             "CURRENT": os.getcwd(),
82         }
83         self.cus_vars = {}
84
85     def _pp_include(self, xml_str: str) -> str:
86         include_regex = r"(<\?include([\w\s\\/.:_-]+)\s*\?>)"
87         matches = re.findall(include_regex, xml_str)
88         for group_inc, group_xml in matches:
89             inc_file_path = group_xml.strip()
90             with open(inc_file_path, "r", encoding="utf-8") as inc_file:
91                 inc_file_content = inc_file.read()
92                 xml_str = xml_str.replace(group_inc, inc_file_content)
93         return xml_str
94
95     def _pp_env_var(self, xml_str: str) -> str:
96         envvar_regex = r"(\$\(env\.(\w+)\))"
97         matches = re.findall(envvar_regex, xml_str)
98         for group_env, group_var in matches:
99             xml_str = xml_str.replace(group_env, os.environ[group_var])
100         return xml_str
101
102     def _pp_sys_var(self, xml_str: str) -> str:
103         sysvar_regex = r"(\$\(sys\.(\w+)\))"
104         matches = re.findall(sysvar_regex, xml_str)
105         for group_sys, group_var in matches:
106             xml_str = xml_str.replace(group_sys, self.sys_vars[group_var])
107         return xml_str
108
109     def _pp_cus_var(self, xml_str: str) -> str:
110         define_regex = r"(<\?define\s*(\w+)\s*=\s*([\w\s\"]+)\s*\?>)"
111         matches = re.findall(define_regex, xml_str)
112         for group_def, group_name, group_var in matches:
113             group_name = group_name.strip()
114             group_var = group_var.strip().strip("\"")
115             self.cus_vars[group_name] = group_var
116             xml_str = xml_str.replace(group_def, "")
117         cusvar_regex = r"(\$\(var\.(\w+)\))"
118         matches = re.findall(cusvar_regex, xml_str)
119         for group_cus, group_var in matches:
120             xml_str = xml_str.replace(
121                 group_cus,
122                 self.cus_vars.get(group_var, "")
123             )
124         return xml_str
125
126     def _pp_foreach(self, xml_str: str) -> str:
127         foreach_regex = r"(<\?foreach\s+(\w+)\s+in\s+([\w;]+)\s*\?>(.*)<\?endforeach\?>)"
128         matches = re.findall(foreach_regex, xml_str)
129         for group_for, group_name, group_vars, group_text in matches:
130             group_texts = ""
131             for var in group_vars.split(";"):
132                 self.cus_vars[group_name] = var
133                 group_texts += self._pp_cus_var(group_text)
134             xml_str = xml_str.replace(group_for, group_texts)
135         return xml_str
136
137     def _pp_error_warning(self, xml_str: str) -> str:
138         error_regex = r"<\?error\s*\"([^\"]+)\"\s*\?>"
139         matches = re.findall(error_regex, xml_str)
140         for group_var in matches:
141             raise RuntimeError("[Error]: " + group_var)
142         warning_regex = r"(<\?warning\s*\"([^\"]+)\"\s*\?>)"
143         matches = re.findall(warning_regex, xml_str)
144         for group_wrn, group_var in matches:
145             print("[Warning]: " + group_var)
146             xml_str = xml_str.replace(group_wrn, "")
147         return xml_str
148
149     def _pp_if_eval(self, xml_str: str) -> str:
150         ifelif_regex = (
151             r"(<\?(if|elseif)\s*([^\"\s=<>!]+)\s*([!=<>]+)\s*\"*([^\"=<>!]+)\"*\s*\?>)"
152         )
153         matches = re.findall(ifelif_regex, xml_str)
154         for ifelif, tag, left, operator, right in matches:
155             if "<" in operator or ">" in operator:
156                 result = eval(f"{left} {operator} {right}")
157             else:
158                 result = eval(f'"{left}" {operator} "{right}"')
159             xml_str = xml_str.replace(ifelif, f"<?{tag} {result}?>")
160         return xml_str
161
162     def _pp_ifdef_ifndef(self, xml_str: str) -> str:
163         ifndef_regex = r"(<\?(ifdef|ifndef)\s*([\w]+)\s*\?>)"
164         matches = re.findall(ifndef_regex, xml_str)
165         for group_ifndef, group_tag, group_var in matches:
166             if group_tag == "ifdef":
167                 result = group_var in self.cus_vars
168             else:
169                 result = group_var not in self.cus_vars
170             xml_str = xml_str.replace(group_ifndef, f"<?if {result}?>")
171         return xml_str
172
173     def _pp_if_elseif(self, xml_str: str) -> str:
174         if_elif_else_regex = (
175             r"(<\?if\s(True|False)\?>"
176             r"(.*?)"
177             r"<\?elseif\s(True|False)\?>"
178             r"(.*?)"
179             r"<\?else\?>"
180             r"(.*?)"
181             r"<\?endif\?>)"
182         )
183         if_else_regex = (
184             r"(<\?if\s(True|False)\?>"
185             r"(.*?)"
186             r"<\?else\?>"
187             r"(.*?)"
188             r"<\?endif\?>)"
189         )
190         if_regex = r"(<\?if\s(True|False)\?>(.*?)<\?endif\?>)"
191         matches = re.findall(if_elif_else_regex, xml_str, re.DOTALL)
192         for (group_full, group_if, group_if_elif, group_elif,
193              group_elif_else, group_else) in matches:
194             result = ""
195             if group_if == "True":
196                 result = group_if_elif
197             elif group_elif == "True":
198                 result = group_elif_else
199             else:
200                 result = group_else
201             xml_str = xml_str.replace(group_full, result)
202         matches = re.findall(if_else_regex, xml_str, re.DOTALL)
203         for group_full, group_if, group_if_else, group_else in matches:
204             result = ""
205             if group_if == "True":
206                 result = group_if_else
207             else:
208                 result = group_else
209             xml_str = xml_str.replace(group_full, result)
210         matches = re.findall(if_regex, xml_str, re.DOTALL)
211         for group_full, group_if, group_text in matches:
212             result = ""
213             if group_if == "True":
214                 result = group_text
215             xml_str = xml_str.replace(group_full, result)
216         return xml_str
217
218     def _pp_command(self, xml_str: str) -> str:
219         cmd_regex = r"(<\?cmd\s*\"([^\"]+)\"\s*\?>)"
220         matches = re.findall(cmd_regex, xml_str)
221         for group_cmd, group_exec in matches:
222             output = subprocess.check_output(
223                 group_exec, shell=True,
224                 text=True, stderr=subprocess.STDOUT
225             )
226             xml_str = xml_str.replace(group_cmd, output)
227         return xml_str
228
229     def _pp_blanks(self, xml_str: str) -> str:
230         right_blank_regex = r">[\n\s\t\r]*"
231         left_blank_regex = r"[\n\s\t\r]*<"
232         xml_str = re.sub(right_blank_regex, ">", xml_str)
233         xml_str = re.sub(left_blank_regex, "<", xml_str)
234         return xml_str
235
236     def preprocess(self, xml_str: str) -> str:
237         fns = [
238             self._pp_blanks,
239             self._pp_include,
240             self._pp_foreach,
241             self._pp_env_var,
242             self._pp_sys_var,
243             self._pp_cus_var,
244             self._pp_if_eval,
245             self._pp_ifdef_ifndef,
246             self._pp_if_elseif,
247             self._pp_command,
248             self._pp_error_warning,
249         ]
250
251         while True:
252             changed = False
253             for func in fns:
254                 out_xml = func(xml_str)
255                 if not changed and out_xml != xml_str:
256                     changed = True
257                 xml_str = out_xml
258             if not changed:
259                 break
260
261         return xml_str
262
263
264 def preprocess_xml(path: str) -> str:
265     with open(path, "r", encoding="utf-8") as original_file:
266         input_xml = original_file.read()
267
268         proc = Preprocessor()
269         return proc.preprocess(input_xml)
270
271
272 def save_xml(xml_str: str, path: Optional[str]):
273     xml = minidom.parseString(xml_str)
274     with open(path, "w", encoding="utf-8") if path else sys.stdout as output_file:
275         output_file.write(xml.toprettyxml())
276
277
278 def main():
279     if len(sys.argv) < 2:
280         print("Usage: xml-preprocessor input.xml [output.xml]")
281         sys.exit(1)
282
283     output_file = None
284     if len(sys.argv) == 3:
285         output_file = sys.argv[2]
286
287     input_file = sys.argv[1]
288     output_xml = preprocess_xml(input_file)
289     save_xml(output_xml, output_file)
290
291
292 if __name__ == "__main__":
293     main()