import lxml.etree as ET from pathlib import Path import click from datetime import datetime def write_xml_data(root: ET.Element, file_path: Path, pretty: bool = True) -> None: if file_path.exists(): file_path.rename(str(file_path) + f".{datetime.now().strftime('%Y-%m-%d.%H%M%S')}.bak") xml_str = ET.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=pretty) if pretty: # Parse the XML string to ensure proper indentation parser = ET.XMLParser(remove_blank_text=True, strip_cdata=False) root = ET.fromstring(xml_str, parser=parser) xml_str = ET.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=pretty) file_path.write_bytes(xml_str) def sanitize_name(name: str) -> str: """ Sanitize a name by removing all non-letter characters except hyphens and Scandinavian letters. """ # Replace Scandinavian letters name = name.replace('ä', 'a').replace('ö', 'o') name = name.replace('Ä', 'A').replace('Ö', 'O') # Remove all non-letter characters except hyphens return ''.join(c for c in name if c.isalpha() or c == '-') def add_connection(root: ET.Element, name: str, attr_list: list[str], description: str = "Default description") -> ET.Element: """ Add a new connection parameter to the XML configuration. Args: root (ET.Element): The root element of the XML configuration. name (str): The name of the connection parameter. description (str, optional): The description of the connection parameter. Defaults to "Default description". Returns: ET.Element: The modified root element with the new connection parameter added. """ connection_parameters = root.find("ConnectionParameters") if connection_parameters is None: connection_parameters = ET.SubElement(root, "ConnectionParameters") ET.SubElement(connection_parameters, "ConnectionParameter", Name=f"VarhaUrl{name}", Description=f"Codeserver url for {description}") initialization = root.find("Initialization") if initialization is not None: predefined_commands = initialization.find("PredefinedCommands") if predefined_commands is None: predefined_commands = ET.SubElement(initialization, "PredefinedCommands") ET.SubElement(predefined_commands, "Command", Name=f"Get-AllCodeServerPages{name}", Description=f"Get all pages for {name}") ET.SubElement(predefined_commands, "Command", Name=f"Get-CodeServerPage{name}", Description=f"Get a specific page for {name}") schema = root.find("Schema") if schema is not None: new_class = ET.SubElement(schema, "Class", Name=f"CodeServerObject{name}") properties = ET.SubElement(new_class, "Properties") property = ET.SubElement(properties, "Property", Name="conceptCodeId", DataType="String", IsMultivalue="false", IsAutoFill="false", IsUniqueKey="true") return_binding = ET.SubElement(property, "ReturnBinding") ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-AllCodeServerPages{name}", Path="conceptCodeId") ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-CodeServerPage{name}", Path="conceptCodeId") mapping = ET.SubElement(property, "CommandMappings") ET.SubElement(mapping, "Map", ToCommand=f"Get-CodeServerPages{name}", Parameter="conceptCodeId") for attr in attr_list: property = ET.SubElement(properties, "Property", Name=sanitize_name(attr), DataType="String", IsMultivalue="false", IsAutoFill="false") return_binding = ET.SubElement(property, "ReturnBinding") ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-AllCodeServerPages{name}", Path=sanitize_name(attr)) ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-CodeServerPage{name}", Path=sanitize_name(attr)) read_configuration = ET.SubElement(new_class, "ReadConfiguration") listing_command = ET.SubElement(read_configuration, "ListingCommand", Command=f"Get-AllCodeServerPages{name}") ET.SubElement(listing_command, "SetParameter", Param="Resource", Source="ConnectionParameter", Value=f"VarhaUrl{name}") ET.SubElement(listing_command, "SetParameter", Param="RootProperty", Source="FixedValue", Value="conceptCodes") cmd_squence = ET.SubElement(read_configuration, "CommandSequence") item = ET.SubElement(cmd_squence, "Item", Command=f"Get-CodeServerPage{name}", Order="1") ET.SubElement(item, "SetParameter", Param="Resource", Source="ConnectionParameter", Value=f"VarhaUrl{name}") ET.SubElement(item, "SetParameter", Param="RootProperty", Source="FixedValue", Value="conceptCodes") return root def _generate_xml_configuration() -> ET.Element: """ Generate a default XML configuration for a PowerShell connector. Returns: ET.Element: The root element of the XML configuration. """ root = ET.Element("PowershellConnectorDefinition", Id="CodeServerConnector", Version="0.1", Description="CodeServer REST api connector") ET.SubElement(root, "PluginAssemblies") ET.SubElement(root, "ConnectionParameters") initialization = ET.SubElement(root, "Initialization") ET.SubElement(initialization, "PredefinedCommands") custom_commands = ET.SubElement(initialization, "CustomCommands") cmd = ET.SubElement(custom_commands, "CustomCommand", Name="Initialize-CodeServer") cmd.text = ET.CDATA(r"""param ( ) Import-Module D:\bin\VSHVA\PowerShell\CodeServerConnector.psm1""") environment_initialization = ET.SubElement(initialization, "EnvironmentInitialization") connect = ET.SubElement(environment_initialization, "Connect") command_sequence = ET.SubElement(connect, "CommandSequence") ET.SubElement(command_sequence, "Item", Command="Initialize-CodeServer", Order="1") ET.SubElement(environment_initialization, "Disconnect") ET.SubElement(root, "Schema") return root def get_xml_data(xml_file: Path) -> ET.Element: """ Get XML data from a file. If the file does not exist, generate a default configuration. Args: xml_file (Path): The path to the XML file. Returns: ET.Element: The root element of the XML configuration. """ if not xml_file.exists(): return _generate_xml_configuration() try: parser = ET.XMLParser(strip_cdata=False) tree = ET.parse(xml_file, parser=parser) return tree except ET.XMLSyntaxError as e: raise click.FileError(xml_file, f"Not a valid XML file. {e}")