from argparse import ArgumentParser from csv import reader as csv_reader from pprint import pformat from xml.dom.minidom import parseString as xml_parser from xml.etree.ElementTree import Element as XMLElement from xml.etree.ElementTree import SubElement as XMLSubElement from xml.etree.ElementTree import tostring as element_stringifier class System: """Represents an L-System""" def __init__( self, name: str, base: list[str], axiom: str, substitutions: list[tuple[str, str]], interpretations: list[tuple[str, str]], ): self.name = name.strip() """System name""" self.base = map(str.strip, base) """Set used""" self.axiom = axiom.strip() """Axiom used at the start of iterations""" self.substitutions = [(m.strip(), s.strip()) for m, s in substitutions] """ Substitution for each member of the base, represented as a couple (member, substitution) """ self.interpretations = [(m.strip(), i.strip()) for m, i in interpretations] """ Interpretation for each member of the base, represented as a couple (member, interpretation) """ # Interpretation of extra symbols added if necessary extra_symbols = ["[", "]"] for _, substitution in substitutions: if any(symbol in substitution for symbol in extra_symbols): self.interpretations.extend( [(extra_symbols[0], "STORE"), (extra_symbols[1], "RESTORE")] ) break def __repr__(self): return pformat(object=self.__dict__, compact=True, width=120, sort_dicts=False) def to_xml(self) -> XMLElement: """Convert the current system into an XML element""" system = XMLElement("lsystem") base = XMLSubElement(system, "name") base.text = self.name base = XMLSubElement(system, "base") base.text = "".join(self.base) axiom = XMLSubElement(system, "axiom") axiom.text = self.axiom substitutions = XMLSubElement(system, "substitutions") for member, substitution in self.substitutions: sub_element = XMLSubElement(substitutions, "substitution") sub_element.set("member", member) sub_element.text = substitution interpretations = XMLSubElement(system, "interpretations") for member, interpretation in self.interpretations: inter_element = XMLSubElement(interpretations, "interpretation") inter_element.set("member", member) inter_element.text = interpretation return system def data_reader(path: str, delimiter: str = ","): """Read a CSV file and returns a list of L-System""" res: list[System] = [] with open(path) as csv_file: data = csv_reader(csv_file, delimiter=delimiter) for system in data: if len(system) == 0: continue name = system[0] base = list(system[1]) axiom = system[2] substitutions = [(v, system[3 + i]) for i, v in enumerate(base)] interpretations = [ (v, system[3 + i + len(substitutions)]) for i, v in enumerate(base) ] res.append(System(name, base, axiom, substitutions, interpretations)) return res def lsystems_xml(systems: list[System]) -> XMLElement: """Convert list of L-system structure into XML""" root = XMLElement("lsystems") for system in systems: root.append(system.to_xml()) return root if __name__ == "__main__": parser = ArgumentParser(description="Generate XML representation of L-systems") parser.add_argument( "file", metavar="FILE", help="CSV file containing L-system data" ) parser.add_argument( "-o", "--output", metavar="OUTPUT_FILE", help="Specify output file for XML" ) args = parser.parse_args() # Read data lsystems = data_reader(args.file) # Generate XML xml = lsystems_xml(lsystems) # Add XML model declaration xml_string = ( '\n' + element_stringifier(xml).decode() ) # Output XML dom = xml_parser(xml_string) pretty_xml = dom.toprettyxml(indent=" ", encoding="UTF-8").decode() if args.output: with open(args.output, "w") as output_file: output_file.write(pretty_xml) else: print(pretty_xml)