2
0

xml-preprocess.py 8.9 KB

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