package de.duehl.basics.text.xml;

/*
 * Copyright 2025 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.text.Lines;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse stellt Methoden zur Analyse und Extraktion aus einer XML-Datei eingelesenen Zeilen
 * zur Verfügung.
 *
 * @version 1.01     2025-02-10
 * @author Christian Dühl
 */

public class XmlAnalyser {


    private static final Pattern ANY_PARAMETER_PATTERN = Pattern.compile(
            "[-_A-Za-z0-9]+ *= *(?:\"([^\"]+)\"|(\\d+))");

    private static final Pattern PARAMETER_PATTERN = Pattern.compile(
            "([-_A-Za-z0-9]+) *= *\"([^\"]+)\"");

    private static final Pattern PARAMETER_WITHOUT_QUOTES_PATTERN = Pattern.compile(
            "([-_A-Za-z0-9]+) *= *(\\d+)");

    private XmlAnalyser() {}

    /**
     * Ermittelt alle Zeilen zwischen dem öffnenden und schließenden Tag, das öffnende und
     * schließende Tag inclusive.                                                          <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">
     *             Bar
     *         </bar>
     *     </foo>
     *
     * werden zum Tag "bar" die Zeilen
     *
     *         <bar parameter="7">
     *             Bar
     *         </bar>
     *
     * ermittelt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Tag nach dem gesucht wird.
     * @return Liste mit den relevanten Zeilen.
     */
    public static List<String> getLinesInTag(List<String> lines, String tag) {
        String start1 = "<" + tag + ">";
        String start2 = "<" + tag + " ";
        String end = "</" + tag + ">";

        List<String> tagLines = new ArrayList<>();
        boolean inTag = false;

        for (String line : lines) {
            if (line.contains(start1) || line.contains(start2)) {
                tagLines.add(line);
                inTag = true;
            }
            else if (line.contains(end)) {
                tagLines.add(line);
                inTag = false;
                break; // Um nicht spätere gleichnamige Tags zu erwischen. Wir nehmen nur den
                       // ersten Block!
            }
            else if (inTag) {
                tagLines.add(line);
            }
        }

        if (tagLines.isEmpty()) {
            throw new RuntimeException("In den eingelesenen Zeilen wurde kein Abschnitt mit dem "
                    + "Tag '" + tag + "' gefunden! Die Zeilen sind:\n\t"
                    + Text.joinWithLineBreak(lines));
        }

        return tagLines;
    }

    /**
     * Ermittelt alle Zeilen zwischen dem öffnenden und schließenden Tag, das öffnende und
     * schließende Tag inclusive.                                                          <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">
     *             Bar
     *         </bar>
     *     </foo>
     *
     * werden zum Tag "bar" die Zeilen
     *
     *         <bar parameter="7">
     *             Bar
     *         </bar>
     *
     * ermittelt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Tag nach dem gesucht wird.
     * @return Liste mit den relevanten Zeilen.
     */
    public static Lines getLinesInTag(Lines lines, String tag) {
        List<String> list = lines.getLines();
        List<String> linesInTag = getLinesInTag(list, tag);
        return new Lines(linesInTag);
    }

    /**
     * Ermittelt aus Zeilen mit mehreren Blöcken des gleichen Tags die Listen von
     * Zeilen, die zu jeweils einem Block gehören.                                          <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">
     *             Bar1
     *         </bar>
     *         <bar>
     *             Bar2
     *         </bar>
     *     </foo>
     *
     * werden zum Tag "bar" die beiden Listen
     *
     *         <bar parameter="7">
     *             Bar1
     *         </bar>
     *
     * und
     *
     *         <bar>
     *             Bar2
     *         </bar>
     *
     * ermittelt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Tag nach dem gesucht wird.
     * @return Liste mit den Listen von jeweils zu einem gefundenen Tag gehörigen Zeilen.
     */
    public static List<List<String>> getMultipleLinesInTag(List<String> lines, String tag) {
        List<List<String>> listOfTagLines = getMultipleLinesInTagAllowEmptyList(lines, tag);

        if (listOfTagLines.isEmpty()) {
            throw new RuntimeException("In den eingelesenen Zeilen wurde kein Abschnitt mit dem "
                    + "Tag '" + tag + "' gefunden! Die Zeilen sind:\n\t"
                    + Text.joinWithLineBreak(lines));
        }

        return listOfTagLines;
    }

    /**
     * Ermittelt aus Zeilen mit mehreren Blöcken des gleichen Tags die Listen von
     * Zeilen, die zu jeweils einem Block gehören.                                          <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">
     *             Bar1
     *         </bar>
     *         <bar>
     *             Bar2
     *         </bar>
     *     </foo>
     *
     * werden zum Tag "bar" die beiden Listen
     *
     *         <bar parameter="7">
     *             Bar1
     *         </bar>
     *
     * und
     *
     *         <bar>
     *             Bar2
     *         </bar>
     *
     * ermittelt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Tag nach dem gesucht wird.
     * @return Liste mit den Listen von jeweils zu einem gefundenen Tag gehörigen Zeilen.
     */
    public static List<List<String>> getMultipleLinesInTagAllowEmptyList(List<String> lines, String tag) {
        String start1 = "<" + tag + ">";
        String start2 = "<" + tag + " ";
        String end = "</" + tag + ">";

        List<List<String>> listOfTagLines = new ArrayList<>();
        List<String> tagLines = null;
        boolean inTag = false;

        for (String line : lines) {
            if (line.contains(start1) || line.contains(start2)) {
                tagLines = new ArrayList<>();
                tagLines.add(line);
                if (line.contains(end))  {
                    listOfTagLines.add(tagLines);
                    tagLines = null;
                    inTag = false;
                }
                else {
                    inTag = true;
                }
            }
            else if (line.contains(end)) {
                tagLines.add(line);
                listOfTagLines.add(tagLines);
                tagLines = null;
                inTag = false;
            }
            else if (inTag) {
                tagLines.add(line);
            }
        }

        return listOfTagLines;
    }

    /**
     * Ermittelt aus Zeilen mit mehreren Blöcken des gleichen Tags die Listen von
     * Zeilen, die zu jeweils einem Block gehören.                                          <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">
     *             Bar1
     *         </bar>
     *         <bar>
     *             Bar2
     *         </bar>
     *     </foo>
     *
     * werden zum Tag "bar" die beiden Listen
     *
     *         <bar parameter="7">
     *             Bar1
     *         </bar>
     *
     * und
     *
     *         <bar>
     *             Bar2
     *         </bar>
     *
     * ermittelt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Tag nach dem gesucht wird.
     * @return Liste mit den Listen von jeweils zu einem gefundenen Tag gehörigen Zeilen.
     */
    public static List<Lines> getMultipleLinesInTag(Lines lines, String tag) {
        List<String> list = lines.getLines();
        List<List<String>> multipleLinesInTag = getMultipleLinesInTag(list, tag);
        return Lines.createListOfLinesFromListOfListOfStrings(multipleLinesInTag);
    }

    /**
     * Ermittelt aus Zeilen mit mehreren Blöcken des gleichen Tags die Listen von
     * Zeilen, die zu jeweils einem Block gehören, wobei die Öffnenden und schließenden
     * Tags entfernt werden.                                                                <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">
     *             Bar1
     *         </bar>
     *         <bar>
     *             Bar2
     *         </bar>
     *         <bar parameter="9">Bar3</bar>
     *     </foo>
     *
     * werden zum Tag "bar" die drei Listen
     *
     *             Bar1
     *
     * und
     *
     *             Bar2
     *
     * und
     *
     * Bar3
     *
     * ermittelt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Tag nach dem gesucht wird.
     * @return Liste mit den Listen von jeweils zu einem gefundenen Tag gehörigen Zeilen.
     */
    public static List<List<String>> getMultipleLinesInTagWithoutOpeningAndClosingTag(
            List<String> lines, String tag) {
        List<List<String>> listOfListOfLines = getMultipleLinesInTag(lines, tag);
        for (List<String> listOfLines : listOfListOfLines) {
            if (listOfLines.size() > 2) {
                listOfLines.remove(listOfLines.size() - 1);
                listOfLines.remove(0);
            }
            else {
                String theOneLine = listOfLines.get(0);
                theOneLine = theOneLine.replaceAll("<" + Pattern.quote(tag) + "[^<>]*>", "");
                theOneLine = theOneLine.replaceAll("</" + Pattern.quote(tag) + ">", "");
                theOneLine = theOneLine.trim();
                listOfLines.remove(0);
                listOfLines.add(theOneLine);
            }
        }
        return listOfListOfLines;
    }

    /**
     * Ermittelt aus Zeilen mit mehreren Blöcken des gleichen Tags die Listen von
     * Zeilen, die zu jeweils einem Block gehören, wobei die Öffnenden und schließenden
     * Tags entfernt werden.                                                                <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">
     *             Bar1
     *         </bar>
     *         <bar>
     *             Bar2
     *         </bar>
     *         <bar parameter="9">Bar3</bar>
     *     </foo>
     *
     * werden zum Tag "bar" die drei Listen
     *
     *             Bar1
     *
     * und
     *
     *             Bar2
     *
     * und
     *
     * Bar3
     *
     * ermittelt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Tag nach dem gesucht wird.
     * @return Liste mit den Listen von jeweils zu einem gefundenen Tag gehörigen Zeilen.
     */
    public static List<Lines> getMultipleLinesInTagWithoutOpeningAndClosingTag(
            Lines lines, String tag) {
        List<String> list = lines.getLines();
        List<List<String>> getMultipleLinesInTagWithoutOpeningAndClosingTag =
                getMultipleLinesInTagWithoutOpeningAndClosingTag(list, tag);
        return Lines.createListOfLinesFromListOfListOfStrings(
                getMultipleLinesInTagWithoutOpeningAndClosingTag);
    }

    /**
     * Ermittelt die Zeile mit einem Tag in einer Zeile.                                    <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">Bar</bar>
     *     </foo>
     *
     * wird zum Tag "bar" die Zeile
     *
     *         <bar parameter="7">Bar</bar>
     *
     * ermittelt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Tag nach dem gesucht wird.
     * @return Gefundene Zeile.
     */
    public static String getFullTagInLine(List<String> lines, String tag) {
        String start1 = "<" + tag + ">";
        String start2 = "<" + tag + " ";
        String end = "</" + tag + ">";

        for (String line : lines) {
            if ((line.contains(start1) || line.contains(start2)) && line.contains(end)) {
                return line;
            }
        }

        throw new RuntimeException("In den eingelesenen Zeilen wurde keine Zeile mit dem "
                + "Tag '" + tag + "' gefunden! Die Zeilen sind:\n\t"
                    + Text.joinWithLineBreak(lines));
    }

    /**
     * Holt den Inhalt zwischen öffnendem und schließendem Tag heraus.                      <br><br>
     *
     * Aus
     *
     *         <bar parameter="7">Bar</bar>
     *
     * wird zum Tag "bar" der Inhalt "Bar" ermittelt.
     *
     * @param line
     *            Teile mit dem Tag.
     * @param tag
     *            Name des Tags.
     * @return Inhalt.
     */
    public static String getContentInTag(String line, String tag) {
        String start1 = "<" + tag + ">";
        String start2 = "<" + tag + " ";
        String end = "</" + tag + ">";

        int endPosition = line.indexOf(end);
        if (endPosition == -1) {
            throw new RuntimeException("Keinen schließenden Tag '" + end + "' in der Zeile '"
                    + line + "' gefunden!");
        }
        int startPosition1 = line.indexOf(start1);
        int startPosition2 = line.indexOf(start2);
        if (startPosition1 == -1 && startPosition2 == -1) {
            throw new RuntimeException("Keinen öffnenden Tag (weder '" + start1 + "'  noch '"
                    + start2 + "') in der Zeile '" + line + "' gefunden!");
        }
        int startPosition = 0;
        if (startPosition1 > 0) {
            startPosition = startPosition1 + start1.length();
        }
        else {
            startPosition = line.indexOf(">", startPosition2);
            if (startPosition == -1) {
                throw new RuntimeException("Es wurde kein '>' hinter dem öffnenden Tag '"
                        + start2 + "' in der Zeile '" + line + "' gefunden!");
            }
            ++startPosition;
        }

        String content = line.substring(startPosition, endPosition);
        return content;
    }

    /**
     * Holt den Inhalt zwischen öffnendem und schließendem Tag heraus.                      <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">Bar</bar>
     *     </foo>
     *
     * wird zum Tag "bar" der Inhalt "Bar" ermittelt.
     *
     * @param xml
     *            Zeilen aus einer XML-Datei.
     * @param tag
     *            Name des Tags.
     * @return Inhalt.
     */
    public static String getContentInFullTagLine(List<String> xml, String tag) {
        String line = XmlAnalyser.getFullTagInLine(xml, tag);
        String content = XmlAnalyser.getContentInTag(line, tag);
        return content;
    }

    /**
     * Holt den Inhalt zwischen öffnendem und schließendem Tag heraus.                      <br><br>
     *
     * Aus
     *     <foo>
     *         <bar parameter="7">Bar</bar>
     *     </foo>
     *
     * wird zum Tag "bar" der Inhalt "Bar" ermittelt.
     *
     * @param xml
     *            Zeilen aus einer XML-Datei.
     * @param tag
     *            Name des Tags.
     * @return Inhalt oder der leere String bei Fehlern.
     */
    public static String getContentInFullTagLineErrorToBlank(List<String> xml, String tag) {
        try {
            return getContentInFullTagLine(xml, tag);
        }
        catch (Exception exception) {
            return "";
        }
    }

    /**
     * Ermittelt die XML-Parameter aus einem öffnenden XML-Tag.
     *
     * @param line
     *            Zeile mit dem öffnenden XML-Tag.
     * @param tag
     *            Zu suchender Tag.
     * @return Liste mit den Parametern.
     */
    public static List<NamedXmlParameter> getParametersFromOpeningTag(String line, String tag) {
        String start2 = "<" + tag + " ";
        int startOfOpeningTagIndex = line.indexOf(start2);
        if (startOfOpeningTagIndex == -1) {
            throw new RuntimeException("Zum Tag '" + tag + "' wurd das öffnenden Tag '" + start2
                    + "' nicht gefunden in der Zeile '" + line + "'!");
        }
        int endOfOpeningTagIndex = line.indexOf(">", startOfOpeningTagIndex);
        if (endOfOpeningTagIndex == -1) {
            throw new RuntimeException("Nach dem öffnenden Tag '" + start2
                    + "' wurde kein '>' mehr gefunden in der Zeile '" + line + "'!");
        }

        //String openingTag = line.substring(startOfOpeningTagIndex, endOfOpeningTagIndex);
        String paramPart = line.substring(
                startOfOpeningTagIndex + 2 + tag.length(), // "<tag " kommt vorn noch weg.
                endOfOpeningTagIndex);                 // ">" kommt hinten noch weg.

        List<NamedXmlParameter> parameters = new ArrayList<>();

        Matcher matcher = ANY_PARAMETER_PATTERN.matcher(paramPart);
        while (matcher.find()) {
            String found = matcher.group();
            Matcher parameterMatcher = PARAMETER_PATTERN.matcher(found);
            if (parameterMatcher.find()) {
                String name = parameterMatcher.group(1);
                String content = parameterMatcher.group(2);
                NamedXmlParameter parameter = new NamedXmlParameter(name, content);
                parameters.add(parameter);
            }
            else {
                Matcher parameterMatcherWithoutQuotes =
                        PARAMETER_WITHOUT_QUOTES_PATTERN.matcher(found);
                if (parameterMatcherWithoutQuotes.find()) {
                    String name = parameterMatcherWithoutQuotes.group(1);
                    String content = parameterMatcherWithoutQuotes.group(2);
                    NamedXmlParameter parameter = new NamedXmlParameter(name, content);
                    parameters.add(parameter);
                }
                else {
                    throw new RuntimeException("Ein gefundener Parameter war weder eine mit noch "
                            + "ohne Anführungszeichen.\n"
                            + "\t" + "line      = " + line + "\n"
                            + "\t" + "tag       = " + tag + "\n"
                            + "\t" + "paramPart = " + paramPart + "\n"
                            + "\t" + "found     = " + found + "\n"
                            );
                }
            }
        }

        /*

        Matcher parameterMatcher = PARAMETER_PATTERN.matcher(paramPart);
        while (parameterMatcher.find()) {
            String name = parameterMatcher.group(1);
            String content = parameterMatcher.group(2);
            NamedXmlParameter parameter = new NamedXmlParameter(name, content);
            parameters.add(parameter);
        }

        Matcher parameterMatcherWithoutQuotes = PARAMETER_WITHOUT_QUOTES_PATTERN.matcher(paramPart);
        while (parameterMatcherWithoutQuotes.find()) {
            String name = parameterMatcherWithoutQuotes.group(1);
            String content = parameterMatcherWithoutQuotes.group(2);
            NamedXmlParameter parameter = new NamedXmlParameter(name, content);
            parameters.add(parameter);
        }
        */

        return parameters;
    }

    /**
     * Ermittelt die erste Zeile eines öffnenden Tags
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Zu suchender Tag.
     * @return Zeile mit dem öffnenden Tag.
     */
    public static String determineLineWithOpeningTag(List<String> lines, String tag) {
        for (String line : lines) {
            if (line.contains("<" + tag)) {
                return line;
            }
        }
        throw new RuntimeException("Es wurde kein öffnendes Tag '" + tag + "' in den übergebenen "
                + "Zeilen gefunden.\n\tlines = " + lines);
    }

    /**
     * Gibt an, ob es den öffnenden Tag in den übergebenen Zeilen gibt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Zu suchender Tag.
     * @return Wahrheitswert
     */
    public static boolean existsOpeningTag(List<String> lines, String tag) {
        for (String line : lines) {
            if (existsOpeningTag(line, tag)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gibt an, ob es den öffnenden Tag in der übergebenen Zeile gibt.
     *
     * @param line
     *            Zeile in der gesucht wird.
     * @param tag
     *            Zu suchender Tag.
     * @return Wahrheitswert
     */
    public static boolean existsOpeningTag(String line, String tag) {
        return line.contains("<" + tag);
    }

    /**
     * Gibt an, ob es den schließende Tag in der übergebenen Zeile gibt.
     *
     * @param line
     *            Zeile in der gesucht wird.
     * @param tag
     *            Zu suchender Tag.
     * @return Wahrheitswert
     */
    public static boolean existsClosingTag(String line, String tag) {
        return line.contains("</" + tag);
    }

    /**
     * Ermittelt den ersten Index des schließenden Tags.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Zu suchender öffnender Tag.
     * @return Index der ersten Zeile mit dem schließenden Tag oder -1, wenn keine solche gefunden
     *         wird.
     */
    public static int getFirstOpeningTagIndex(List<String> lines, String tag) {
        return getFirstOpeningTagIndex(lines, tag, 0);
    }

    /**
     * Ermittelt den ersten Index des schließenden Tags.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Zu suchender öffnender Tag.
     * @param startIndex
     *            Indes ab dem begonnen wird zu suchen.
     * @return Index der ersten Zeile mit dem schließenden Tag oder -1, wenn keine solche gefunden
     *         wird.
     */
    public static int getFirstOpeningTagIndex(List<String> lines, String tag, int startIndex) {
        String openingTag1 = "<" + tag + ">";
        String openingTag2 = "<" + tag + " ";
        int index1 = CollectionsHelper.determineIndexOfFirstElementContaining(lines, openingTag1,
                startIndex);
        int index2 = CollectionsHelper.determineIndexOfFirstElementContaining(lines, openingTag2,
                startIndex);
        if (index1 == -1) {
            return index2;
        }
        else if (index2 == -1) {
            return index1;
        }
        else {
            return Math.min(index1, index2);
        }
    }

    /**
     * Ermittelt den ersten Index des schließenden Tags.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Zu suchender schließender Tag (ohne Slash davor).
     * @return Index der ersten Zeile mit dem schließenden Tag oder -1, wenn keine solche gefunden
     *         wird.
     */
    public static int getFirstClosingTagIndex(List<String> lines, String tag) {
        return getFirstClosingTagIndex(lines, tag, 0);
    }

    /**
     * Ermittelt den ersten Index des schließenden Tags.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Zu suchender schließender Tag (ohne Slash davor).
     * @param startIndex
     *            Indes ab dem begonnen wird zu suchen.
     * @return Index der ersten Zeile mit dem schließenden Tag oder -1, wenn keine solche gefunden
     *         wird.
     */
    public static int getFirstClosingTagIndex(List<String> lines, String tag, int startIndex) {
        String closingTag = "</" + tag;
        return CollectionsHelper.determineIndexOfFirstElementContaining(lines, closingTag,
                startIndex);
    }

    /**
     * Ermittelt alle Zeilenindices, in denen der öffnende Tag vorkommt.
     *
     * @param lines
     *            Zeilen in denen gesucht wird.
     * @param tag
     *            Zu suchender öffnender Tag.
     * @return Liste mit den Indices.
     */
    public static List<Integer> getAllOpeningTagIndices(List<String> lines, String tag) {
        List<Integer> indices = new ArrayList<>();

        boolean searching = true;
        int searchIndex = 0;
        while (searching) {
            int index = getFirstOpeningTagIndex(lines, tag, searchIndex);
            if (index == -1) {
                searching = false;
            }
            else {
                indices.add(index);
                searchIndex = index + 1;
            }
        }

        return indices;
    }

    /**
     * Teilt eine Liste an den Stellen auf, wo eine Zeile den übergebenen Tag enthält.
     *
     * @param lines
     *            Zeilen die aufgeteilt werden sollen.
     * @param tag
     *            Zu suchender öffnender Tag.
     * @return Liste mit den Listen von Zeilen. Ist der Tag nicht enthalten, so enthält die Liste
     *         nur ein Element, nämlich die ursprünglich übergebene Liste.
     */
    public static List<List<String>> cutAtOpeningTag(List<String> lines, String tag) {
        List<List<String>> partLists = new ArrayList<>();

        List<Integer> tagStartLineIndices = getAllOpeningTagIndices(lines, tag);

        if (tagStartLineIndices.isEmpty()) {
            partLists.add(lines);
        }
        else {
            int firstIndex = tagStartLineIndices.get(0);
            if (firstIndex > 0) {
                List<String> partList = CollectionsHelper.sublist(lines, 0, firstIndex);
                partLists.add(partList);
            }

            for (int indexIndex = 0; indexIndex < tagStartLineIndices.size() - 1; ++indexIndex) {
                int startIndex = tagStartLineIndices.get(indexIndex);
                int endIndex = tagStartLineIndices.get(indexIndex + 1);
                List<String> partList = CollectionsHelper.sublist(lines, startIndex, endIndex);
                partLists.add(partList);
            }

            // TODO kann man mit Java 21 und getLast() vereinfachen
            int lastIndex = tagStartLineIndices.get(tagStartLineIndices.size() - 1);
            List<String> lastPartList = CollectionsHelper.sublist(lines, lastIndex, lines.size());
            partLists.add(lastPartList);
        }

        return partLists;
    }

    /**
     * Falls in der ersten Zeile der übergebene Tag geöffnet und in der lezten Zeile der
     * übergebende Tag geschlossen wird, werden die inneren Zeilen (ohne die erste und letzte)
     * zurückgegeben, andrenfalls wird eine leere Liste zurückgegeben.
     *
     * @param lines
     *            Die Liste der Zeilen deren innere Zeilen man gern haben möchte.
     * @param tag
     *            Zu suchender öffnender Tag in der ersten und schließender Tag in der letzten
     *            Zeile.
     * @return Liste mit den inneren Zeilen oder eine leere Liste, falls der Tag nicht in erster
     *         und letzter Zeile gefunden wurde.
     */
    public static List<String> getInnerPartIfStartsAndEndsWithTag(List<String> lines, String tag) {
        if (lines.isEmpty()) {
            throw new RuntimeException("Wurde mit leerer Liste aufgerufen.");
        }

        if (linesStartsWithTag(lines, tag) && linesEndsWithTag(lines, tag)) {
            List<String> innerLines = CollectionsHelper.sublistWithoutFirstAndLastLine(lines);
            return innerLines;
        }
        else {
            return new ArrayList<>();
        }
    }

    /**
     * Gibt an, ob die übergebenen Zeilen mit dem gewünschten öffnenden Tag anfangen.
     *
     * @param lines
     *            Die Liste der Zeilen deren innere Zeilen man gern haben möchte.
     * @param tag
     *            Zu suchender öffnender Tag in der ersten Zeile.
     * @return Wahrheitswert.
     */
    public static boolean linesStartsWithTag(List<String> lines, String tag) {
        if (lines.isEmpty()) {
            throw new RuntimeException("Wurde mit leerer Liste aufgerufen.");
        }

        String openingTag1 = "<" + tag + ">";
        String openingTag2 = "<" + tag + " ";

        String firstLine = lines.get(0);
        return firstLine.contains(openingTag1) || firstLine.contains(openingTag2);
    }

    /**
     * Gibt an, ob die übergebenen Zeilen mit dem gewünschten schließenden Tag aufhören.
     *
     * @param lines
     *            Die Liste der Zeilen deren innere Zeilen man gern haben möchte.
     * @param tag
     *            Zu suchender schließender Tag in der letzten Zeile.
     * @return Wahrheitswert.
     */
    public static boolean linesEndsWithTag(List<String> lines, String tag) {
        if (lines.isEmpty()) {
            throw new RuntimeException("Wurde mit leerer Liste aufgerufen.");
        }

        // TODO kann man mit Java 21 und getLast() vereinfachen - Suchen nach ".size() - 1" !
        String lastLine = lines.get(lines.size() - 1);

        String closingTag = "</" + tag;

        return lastLine.contains(closingTag);
    }

}
