|
@@ -0,0 +1,293 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+#
|
|
|
+# Copyright (c) 2017-2019 Tony Su
|
|
|
+# Copyright (c) 2023 Red Hat, Inc.
|
|
|
+#
|
|
|
+# SPDX-License-Identifier: MIT
|
|
|
+#
|
|
|
+# Adapted from https://github.com/peitaosu/XML-Preprocessor
|
|
|
+#
|
|
|
+"""This is a XML Preprocessor which can be used to process your XML file before
|
|
|
+you use it, to process conditional statements, variables, iteration
|
|
|
+statements, error/warning, execute command, etc.
|
|
|
+
|
|
|
+## XML Schema
|
|
|
+
|
|
|
+### Include Files
|
|
|
+```
|
|
|
+<?include path/to/file ?>
|
|
|
+```
|
|
|
+
|
|
|
+### Variables
|
|
|
+```
|
|
|
+$(env.EnvironmentVariable)
|
|
|
+
|
|
|
+$(sys.SystemVariable)
|
|
|
+
|
|
|
+$(var.CustomVariable)
|
|
|
+```
|
|
|
+
|
|
|
+### Conditional Statements
|
|
|
+```
|
|
|
+<?if ?>
|
|
|
+
|
|
|
+<?ifdef ?>
|
|
|
+
|
|
|
+<?ifndef ?>
|
|
|
+
|
|
|
+<?else?>
|
|
|
+
|
|
|
+<?elseif ?>
|
|
|
+
|
|
|
+<?endif?>
|
|
|
+```
|
|
|
+
|
|
|
+### Iteration Statements
|
|
|
+```
|
|
|
+<?foreach VARNAME in 1;2;3?>
|
|
|
+ $(var.VARNAME)
|
|
|
+<?endforeach?>
|
|
|
+```
|
|
|
+
|
|
|
+### Errors and Warnings
|
|
|
+```
|
|
|
+<?error "This is error message!" ?>
|
|
|
+
|
|
|
+<?warning "This is warning message!" ?>
|
|
|
+```
|
|
|
+
|
|
|
+### Commands
|
|
|
+```
|
|
|
+<? cmd "echo hello world" ?>
|
|
|
+```
|
|
|
+"""
|
|
|
+
|
|
|
+import os
|
|
|
+import platform
|
|
|
+import re
|
|
|
+import subprocess
|
|
|
+import sys
|
|
|
+from typing import Optional
|
|
|
+from xml.dom import minidom
|
|
|
+
|
|
|
+
|
|
|
+class Preprocessor():
|
|
|
+ """This class holds the XML preprocessing state"""
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self.sys_vars = {
|
|
|
+ "ARCH": platform.architecture()[0],
|
|
|
+ "SOURCE": os.path.abspath(__file__),
|
|
|
+ "CURRENT": os.getcwd(),
|
|
|
+ }
|
|
|
+ self.cus_vars = {}
|
|
|
+
|
|
|
+ def _pp_include(self, xml_str: str) -> str:
|
|
|
+ include_regex = r"(<\?include([\w\s\\/.:_-]+)\s*\?>)"
|
|
|
+ matches = re.findall(include_regex, xml_str)
|
|
|
+ for group_inc, group_xml in matches:
|
|
|
+ inc_file_path = group_xml.strip()
|
|
|
+ with open(inc_file_path, "r", encoding="utf-8") as inc_file:
|
|
|
+ inc_file_content = inc_file.read()
|
|
|
+ xml_str = xml_str.replace(group_inc, inc_file_content)
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_env_var(self, xml_str: str) -> str:
|
|
|
+ envvar_regex = r"(\$\(env\.(\w+)\))"
|
|
|
+ matches = re.findall(envvar_regex, xml_str)
|
|
|
+ for group_env, group_var in matches:
|
|
|
+ xml_str = xml_str.replace(group_env, os.environ[group_var])
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_sys_var(self, xml_str: str) -> str:
|
|
|
+ sysvar_regex = r"(\$\(sys\.(\w+)\))"
|
|
|
+ matches = re.findall(sysvar_regex, xml_str)
|
|
|
+ for group_sys, group_var in matches:
|
|
|
+ xml_str = xml_str.replace(group_sys, self.sys_vars[group_var])
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_cus_var(self, xml_str: str) -> str:
|
|
|
+ define_regex = r"(<\?define\s*(\w+)\s*=\s*([\w\s\"]+)\s*\?>)"
|
|
|
+ matches = re.findall(define_regex, xml_str)
|
|
|
+ for group_def, group_name, group_var in matches:
|
|
|
+ group_name = group_name.strip()
|
|
|
+ group_var = group_var.strip().strip("\"")
|
|
|
+ self.cus_vars[group_name] = group_var
|
|
|
+ xml_str = xml_str.replace(group_def, "")
|
|
|
+ cusvar_regex = r"(\$\(var\.(\w+)\))"
|
|
|
+ matches = re.findall(cusvar_regex, xml_str)
|
|
|
+ for group_cus, group_var in matches:
|
|
|
+ xml_str = xml_str.replace(
|
|
|
+ group_cus,
|
|
|
+ self.cus_vars.get(group_var, "")
|
|
|
+ )
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_foreach(self, xml_str: str) -> str:
|
|
|
+ foreach_regex = r"(<\?foreach\s+(\w+)\s+in\s+([\w;]+)\s*\?>(.*)<\?endforeach\?>)"
|
|
|
+ matches = re.findall(foreach_regex, xml_str)
|
|
|
+ for group_for, group_name, group_vars, group_text in matches:
|
|
|
+ group_texts = ""
|
|
|
+ for var in group_vars.split(";"):
|
|
|
+ self.cus_vars[group_name] = var
|
|
|
+ group_texts += self._pp_cus_var(group_text)
|
|
|
+ xml_str = xml_str.replace(group_for, group_texts)
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_error_warning(self, xml_str: str) -> str:
|
|
|
+ error_regex = r"<\?error\s*\"([^\"]+)\"\s*\?>"
|
|
|
+ matches = re.findall(error_regex, xml_str)
|
|
|
+ for group_var in matches:
|
|
|
+ raise RuntimeError("[Error]: " + group_var)
|
|
|
+ warning_regex = r"(<\?warning\s*\"([^\"]+)\"\s*\?>)"
|
|
|
+ matches = re.findall(warning_regex, xml_str)
|
|
|
+ for group_wrn, group_var in matches:
|
|
|
+ print("[Warning]: " + group_var)
|
|
|
+ xml_str = xml_str.replace(group_wrn, "")
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_if_eval(self, xml_str: str) -> str:
|
|
|
+ ifelif_regex = (
|
|
|
+ r"(<\?(if|elseif)\s*([^\"\s=<>!]+)\s*([!=<>]+)\s*\"*([^\"=<>!]+)\"*\s*\?>)"
|
|
|
+ )
|
|
|
+ matches = re.findall(ifelif_regex, xml_str)
|
|
|
+ for ifelif, tag, left, operator, right in matches:
|
|
|
+ if "<" in operator or ">" in operator:
|
|
|
+ result = eval(f"{left} {operator} {right}")
|
|
|
+ else:
|
|
|
+ result = eval(f'"{left}" {operator} "{right}"')
|
|
|
+ xml_str = xml_str.replace(ifelif, f"<?{tag} {result}?>")
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_ifdef_ifndef(self, xml_str: str) -> str:
|
|
|
+ ifndef_regex = r"(<\?(ifdef|ifndef)\s*([\w]+)\s*\?>)"
|
|
|
+ matches = re.findall(ifndef_regex, xml_str)
|
|
|
+ for group_ifndef, group_tag, group_var in matches:
|
|
|
+ if group_tag == "ifdef":
|
|
|
+ result = group_var in self.cus_vars
|
|
|
+ else:
|
|
|
+ result = group_var not in self.cus_vars
|
|
|
+ xml_str = xml_str.replace(group_ifndef, f"<?if {result}?>")
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_if_elseif(self, xml_str: str) -> str:
|
|
|
+ if_elif_else_regex = (
|
|
|
+ r"(<\?if\s(True|False)\?>"
|
|
|
+ r"(.*?)"
|
|
|
+ r"<\?elseif\s(True|False)\?>"
|
|
|
+ r"(.*?)"
|
|
|
+ r"<\?else\?>"
|
|
|
+ r"(.*?)"
|
|
|
+ r"<\?endif\?>)"
|
|
|
+ )
|
|
|
+ if_else_regex = (
|
|
|
+ r"(<\?if\s(True|False)\?>"
|
|
|
+ r"(.*?)"
|
|
|
+ r"<\?else\?>"
|
|
|
+ r"(.*?)"
|
|
|
+ r"<\?endif\?>)"
|
|
|
+ )
|
|
|
+ if_regex = r"(<\?if\s(True|False)\?>(.*?)<\?endif\?>)"
|
|
|
+ matches = re.findall(if_elif_else_regex, xml_str, re.DOTALL)
|
|
|
+ for (group_full, group_if, group_if_elif, group_elif,
|
|
|
+ group_elif_else, group_else) in matches:
|
|
|
+ result = ""
|
|
|
+ if group_if == "True":
|
|
|
+ result = group_if_elif
|
|
|
+ elif group_elif == "True":
|
|
|
+ result = group_elif_else
|
|
|
+ else:
|
|
|
+ result = group_else
|
|
|
+ xml_str = xml_str.replace(group_full, result)
|
|
|
+ matches = re.findall(if_else_regex, xml_str, re.DOTALL)
|
|
|
+ for group_full, group_if, group_if_else, group_else in matches:
|
|
|
+ result = ""
|
|
|
+ if group_if == "True":
|
|
|
+ result = group_if_else
|
|
|
+ else:
|
|
|
+ result = group_else
|
|
|
+ xml_str = xml_str.replace(group_full, result)
|
|
|
+ matches = re.findall(if_regex, xml_str, re.DOTALL)
|
|
|
+ for group_full, group_if, group_text in matches:
|
|
|
+ result = ""
|
|
|
+ if group_if == "True":
|
|
|
+ result = group_text
|
|
|
+ xml_str = xml_str.replace(group_full, result)
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_command(self, xml_str: str) -> str:
|
|
|
+ cmd_regex = r"(<\?cmd\s*\"([^\"]+)\"\s*\?>)"
|
|
|
+ matches = re.findall(cmd_regex, xml_str)
|
|
|
+ for group_cmd, group_exec in matches:
|
|
|
+ output = subprocess.check_output(
|
|
|
+ group_exec, shell=True,
|
|
|
+ text=True, stderr=subprocess.STDOUT
|
|
|
+ )
|
|
|
+ xml_str = xml_str.replace(group_cmd, output)
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def _pp_blanks(self, xml_str: str) -> str:
|
|
|
+ right_blank_regex = r">[\n\s\t\r]*"
|
|
|
+ left_blank_regex = r"[\n\s\t\r]*<"
|
|
|
+ xml_str = re.sub(right_blank_regex, ">", xml_str)
|
|
|
+ xml_str = re.sub(left_blank_regex, "<", xml_str)
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+ def preprocess(self, xml_str: str) -> str:
|
|
|
+ fns = [
|
|
|
+ self._pp_blanks,
|
|
|
+ self._pp_include,
|
|
|
+ self._pp_foreach,
|
|
|
+ self._pp_env_var,
|
|
|
+ self._pp_sys_var,
|
|
|
+ self._pp_cus_var,
|
|
|
+ self._pp_if_eval,
|
|
|
+ self._pp_ifdef_ifndef,
|
|
|
+ self._pp_if_elseif,
|
|
|
+ self._pp_command,
|
|
|
+ self._pp_error_warning,
|
|
|
+ ]
|
|
|
+
|
|
|
+ while True:
|
|
|
+ changed = False
|
|
|
+ for func in fns:
|
|
|
+ out_xml = func(xml_str)
|
|
|
+ if not changed and out_xml != xml_str:
|
|
|
+ changed = True
|
|
|
+ xml_str = out_xml
|
|
|
+ if not changed:
|
|
|
+ break
|
|
|
+
|
|
|
+ return xml_str
|
|
|
+
|
|
|
+
|
|
|
+def preprocess_xml(path: str) -> str:
|
|
|
+ with open(path, "r", encoding="utf-8") as original_file:
|
|
|
+ input_xml = original_file.read()
|
|
|
+
|
|
|
+ proc = Preprocessor()
|
|
|
+ return proc.preprocess(input_xml)
|
|
|
+
|
|
|
+
|
|
|
+def save_xml(xml_str: str, path: Optional[str]):
|
|
|
+ xml = minidom.parseString(xml_str)
|
|
|
+ with open(path, "w", encoding="utf-8") if path else sys.stdout as output_file:
|
|
|
+ output_file.write(xml.toprettyxml())
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ if len(sys.argv) < 2:
|
|
|
+ print("Usage: xml-preprocessor input.xml [output.xml]")
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+ output_file = None
|
|
|
+ if len(sys.argv) == 3:
|
|
|
+ output_file = sys.argv[2]
|
|
|
+
|
|
|
+ input_file = sys.argv[1]
|
|
|
+ output_xml = preprocess_xml(input_file)
|
|
|
+ save_xml(output_xml, output_file)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|