Skip to content
Snippets Groups Projects
Select Git revision
  • master
  • feature/build
  • develop
  • VA_v2022a
  • v2021.a
  • v2020.a
  • v2019.a
  • v2018.b
  • v2017.c
  • v2017.a
  • v2016.a
11 results

VAUnity.cs

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    document_aircraft_xml.py 7.07 KiB
    #!python
    # Copyright (c) 2023 S. Oberschwendtner.
    #
    #
    # This program is free software: you can redistribute it and/or modify
    # it under the terms of the GNU General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this program.  If not, see <https://www.gnu.org/licenses/>.
    
    """! @brief Script for converting an aircraft XML file to a content page."""
    ##
    # @file document_aircraft_xml.py
    #
    # @brief Script for converting an aircraft XML file to a content page for the UNICADO homepage.
    #
    # @section description_document_aircraft_xml Description
    # This script parse the entries of an aircraft XML file and extracts
    # the entries and documents them using their description tags.
    # The output is a markdown file which can be used for the documentation.
    #
    # @section libraries_document_aircraft_xml Libraries/Modules
    # - argparse standard library (https://docs.python.org/3/library/argparse.html)
    #   - Parser for command-line options, arguments and sub-commands.
    # - xml standard library (https://docs.python.org/3/library/xml.html)
    #   - XML Processing Modules.
    # - pathlib standard library (https://docs.python.org/3/library/pathlib.html)
    #   - Object-oriented filesystem paths.
    #
    # @section notes_document_aircraft_xml Notes
    # - None.
    #
    # @section todo_document_aircraft_xml TODO
    # - None.
    #
    # @section authors_document_aircraft_xml Author(s)
    # - Created by S. Oberschwendtner on 19/09/2023.
    
    # === Imports ===
    import argparse
    import xml.etree.ElementTree as ET
    from pathlib import Path
    
    # === Configuration ===
    # Define the format of the output
    FORMAT = {
        "Header": "# {:s}\n> **Description**: {:s}\n",
        "Unit": "> **Unit**: {:s}\n",
        "TableHeader": "| Relative XML Path | Unit | Description |\n|:---|:---:|:---|",
        "Table": "| <nobr>`{:s}` | *{:s}* | {:s} |",
    }
    
    # Which keys are used in the XML attributes for which information
    KEYS = {
        "Description": "Desc",
        "Unit": "Unit",
    }
    
    
    # === Classes ===
    class Page:
        """Class for a page.
    
        A page is one top section of the aircraft XML file.
        """
    
        @property
        def max_level_reached(self) -> bool:
            """Returns whether the maximum level has been reached."""
            return self.current_level >= self.max_header_level
    
        def __init__(self, title, max_level):
            """Constructor of the class.
    
            Args:
                title (str): Title of the page.
                max_level (int): Maximum level of the headers.
            """
            self.title = title
            self.max_header_level = max_level
            self.current_level = 0
            self.table_started = False
            self.current_path = Path(".")
            self.sections = [[]]
    
        def create(self, node: ET.Element):
            """Creates the page from the given node.
    
            This function adds all subnodes of the given node to the page.
            It recursively calls itself for all subnodes.
    
            It does not add sections which are already present in the current level.
    
            Args:
                node (ET.Element): Node to start from.
            """
            # Only add the header if it was not already added at this level
            if node.tag in self.sections[self.current_level]:
                return
    
            # Check whether the current entry can be a header
            if not self.max_level_reached:
                self.sections[self.current_level].append(node.tag)
                self.make_header_entry(node)
                self.current_path = Path(node.tag)
            else:
                self.current_path /= node.tag
    
            # Loop through the children and group single entries in a table
            for child in node:
                if len(child) == 0:
                    self.make_table_entry(child)
    
            # Add a new level of sections
            self.current_level += 1
            self.sections.append([])
    
            # Loop again and add the subnodes
            for child in node:
                if len(child) > 0:
                    self.create(child)
    
            # Decrease the level again when finished
            self.current_level -= 1
            self.sections.pop()
            self.current_path = self.current_path.parent
    
        def make_header_entry(self, node: ET.Element):
            """Creates a header entry.
    
            Args:
                node (ET.Element): The current node element.
            """
            # Reset the table when creating a new header
            self.table_started = False
    
            # Create the header with description
            print(
                "\n"
                + self.current_level * "#"
                + FORMAT["Header"].format(
                    node.tag, node.attrib.get(KEYS["Description"], "None")
                )
            )
    
            # Try to add a unit description
            try:
                print(FORMAT["Unit"].format(node.attrib[KEYS["Unit"]]))
            except KeyError:
                pass
    
        def make_table_entry(self, node: ET.Element):
            """Creates a table entry.
    
            Args:
                node (ET.Element): The current node element.
            """
            # Check if the table has already been started
            if not self.table_started:
                self.table_started = True
                print(FORMAT["TableHeader"])
    
            # Since we use pathlib for convenience, we need to convert it to a string
            # and replace the backslashes with forward slashes
            path_name = str(self.current_path / node.tag).replace("\\", "/")
    
            # Create the header with description
            print(
                FORMAT["Table"].format(
                    path_name,
                    node.attrib.get(KEYS["Unit"], "-"),
                    node.attrib.get(KEYS["Description"], "None"),
                )
            )
    
    
    # === Main ===
    def main():
        """
        Main function of the script.
        """
        # Create argument parser
        parser = argparse.ArgumentParser(
            description="Converts an aircraft XML file to a markdown file. The output is streamed to stdout and can be piped to a file."
        )
    
        # Add the filename argument
        parser.add_argument(
            "filename", metavar="filename", type=str, help="The XML file to convert."
        )
    
        # Add the layout arguments
        parser.add_argument(
            "--title",
            metavar="title",
            type=str,
            help="The title of the output page. This also sets the root node which is used to create the document.",
        )
        parser.add_argument(
            "--level",
            metavar="level",
            type=int,
            help="The maximum level nodes to be used as headers.",
        )
    
        # Parse the arguments
        args = parser.parse_args()
    
        # Read the XML file
        tree = ET.parse(args.filename)
        root = tree.getroot()
    
        # Get the configuration parameters
        page = Page(args.title, args.level)
        node = root.find(page.title)
    
        # Check whether the node exists
        if node is None:
            raise Warning("Could not find node with title '" + page.title + "'")
    
        # Start creating the page
        page.create(node)
    
    
    if __name__ == "__main__":
        main()