#     Copyright 2026, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file


"""XML node tree handling

Means to create XML elements from Nuitka tree nodes and to convert the
XML tree to ASCII or output it.
"""

import os

from nuitka.__past__ import BytesIO, StringIO, basestring


def _indent(elem, level=0, more_sibs=False):
    i = "\n"
    if level:
        i += (level - 1) * "  "
    num_kids = len(elem)
    if num_kids:
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
            if level:
                elem.text += "  "
        count = 0
        for kid in elem:
            _indent(kid, level + 1, count < num_kids - 1)
            count += 1
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
            if more_sibs:
                elem.tail += "  "
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i
            if more_sibs:
                elem.tail += "  "

    return elem


def _dedent(elem, level=0):
    if not elem.text or not elem.text.strip():
        elem.text = ""

    for child in elem:
        _dedent(child, level + 1)

    if not elem.tail or not elem.tail.strip():
        elem.tail = ""

    return elem


try:
    import xml.etree.ElementTree

    xml_module = xml.etree.ElementTree

    Element = xml.etree.ElementTree.Element

    def xml_tostring(tree, indent=True, encoding=None):
        if indent:
            _indent(tree)
        elif not indent:
            _dedent(tree)

        return xml_module.tostring(tree, encoding=encoding)

except ImportError:
    xml_module = None
    Element = None
    xml_tostring = None

# TODO: Use the writer to create the XML we output. That should be more
# scalable and/or faster.
# try:
#     from lxml import (
#         xmlfile as xml_writer,  # pylint: disable=I0021,import-error,unused-import
#     )
# except ImportError:
#     xml_writer = None


def convertXmlToBytes(
    tree, indent=True, encoding=None, use_lxml=False, xml_declaration=True
):
    if use_lxml:
        from lxml import etree  # pylint: disable=I0021,import-error

        result = etree.tostring(
            tree,
            encoding="utf8",
            xml_declaration=xml_declaration,
        )

        # Make LXML self-closing tags compatible with Elementree manually,
        # so reports become comparable independent of what's used.
        result = result.replace(b"/>", b" />")

        result = result.rstrip() + b"\n"

        return result

    return xml_tostring(tree, indent=indent, encoding=encoding)


def convertXmlToString(tree, use_lxml=False, xml_declaration=True):
    result = convertXmlToBytes(
        tree,
        encoding="utf8",
        use_lxml=use_lxml,
        xml_declaration=xml_declaration,
    )

    if str is not bytes:
        result = result.decode("utf8")

    return result


def convertStringToXML(text, use_lxml=False):
    if type(text) is str:
        return convertFileToXML(StringIO(text), use_lxml=use_lxml)
    else:
        return convertFileToXML(BytesIO(text), use_lxml=use_lxml)


def convertFileToXML(file_handle, use_lxml=False):
    if isinstance(file_handle, basestring):
        if not os.path.isfile(file_handle):
            return None

    if use_lxml:
        from lxml import etree  # pylint: disable=I0021,import-error

        # TODO: Catch parse error exception to return None as well.
        return etree.parse(file_handle).getroot()
    else:
        try:
            return xml_module.parse(file_handle).getroot()
        except xml.etree.ElementTree.ParseError:
            return None


def appendTreeElement(parent, *args, **kwargs):
    element = Element(*args, **kwargs)

    parent.append(element)

    return element


def dumpTreeXMLToFile(tree, output_file):
    """Write an XML node tree to a file."""

    value = convertXmlToBytes(tree).rstrip()
    output_file.write(value)
    output_file.write(b"\n")


#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the GNU Affero General Public License, Version 3 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        https://www.gnu.org/licenses/agpl-3.0.txt
#
#     See also: "Nuitka Runtime Library Exception, Version 1.0" in file
#     "LICENSE-RUNTIME.txt" for additional permissions granted under Section 7.
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
