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