| #!/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() |