package de.duehl.basics.text.html;

/*
 * 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.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.logic.Pair;
import de.duehl.basics.text.Text;
import de.duehl.basics.text.data.FoundSearch;

/**
 * Diese Klasse stellt Hilfsmethoden im Umgang und der Erzeugung von HTML zur Verfügung.
 *
 * @version 1.01     2025-08-08
 * @author Christian Dühl
 */

public class HtmlTool {

    /** Liste der Ersetzungen von UTF-8-Zeichen und ihrer Entsprechung im HTML-Code. */
    private static final List<Pair<String>> HTML_TO_UTF8 = createHtmlEncodingList();

    //private static final List<Pair<String>> REVERS_HTML_TO_UTF8 = createReverseHtmlEncodingList();

    /**
     * Liste der reservierten HTML5-Tags nach
     * https://developer.mozilla.org/de/docs/Web/HTML/HTML5/HTML5_element_list
     */
    private static final List<String> HTML5_KEY_WORDS = CollectionsHelper.buildLoweredListFrom(
            "html", "title", "base", "link", "meta", "style", "script", "noscript", "body",
            "section", "nav", "aside", "h1", "h2", "h3", "h4", "h5", "h6", "header", "footer",
            "address", "main", "p", "hr", "pre", "blockquote", "ol", "ul", "li", "dl", "dt", "dd",
            "figure", "figcaption", "div", "a", "em", "strong", "small", "s", "cite", "q", "dfn",
            "abbr", "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u", "mark",
            "ruby", "rt", "rp", "bdi", "bdo", "span", "br", "wbr", "ins", "del", "img", "iframe",
            "embed", "object", "param", "video", "audio", "source", "track", "canvas", "map",
            "area", "svg", "math", "table", "caption", "colgroup", "col", "tbody", "thead", "tfoot",
            "tr", "td", "th", "form", "fieldset", "legend", "label", "input", "button", "select",
            "datalist", "optgroup", "option", "textarea", "keygen", "output", "progress", "meter",
            "details", "summary", "command", "menu");

    private HtmlTool() {}

    /**
     * Setzt in einem Text Links in HTML-Links um.
     *
     * @param text
     *            Zu bearbeitender Text.
     * @return umgesetzter Text
     */
    public static String textToHtmlConvertingURLsToLinks(String text) {
        if (text == null) {
            return text;
        }

        return text.replaceAll("(\\A|\\s)"
                + "((?:http|https|ftp|mailto):\\S+[^.,!?]|www\\.\\S+\\.[a-z]{2,3})"
                + "(\\s|\\z|\\.|,|\\!|\\?)",
                "$1<a href=\"$2\">$2</a>$3");
    }

    private final static Pattern FILE_PATTERN = Pattern.compile(
            "(.*(?:\\A|\\s))"                // davor: neue Zeile oder Leerzeichen
            + "("
            + "(?:file\\:)?"                 // optional file: davor
            + "(?:[A-Za-z]\\:[/\\\\]|"       // C:\ oder c:/ oder
            + "[/\\\\]{2})"                  // \\ oder //
            + ".+?\\.[a-zA-Z]{3,4}"          //  sonstiges bis zu einer Endung
            + ")"
            + "((:\\s|\\z|\\.|,|\\!|\\?).*)" // dahinter Leer oder Satzzeichen
                                             // oder Zeilenende
            );

    /**
     * Setzt in einem Text Links in HTML-Links um.
     *
     * @param text
     *            Zu bearbeitender Text.
     * @return umgesetzter Text
     */
    public static String textToHtmlConvertingFilesToLinks(String text) {
        if (text == null || text.isEmpty()) {
            return text;
        }

        String editedText = text;

        Matcher matcher = FILE_PATTERN.matcher(editedText);
        while (matcher.find()) {
            String front = matcher.group(1);
            String file  = matcher.group(2);
            String back  = matcher.group(3);
            String fileSlashes = Text.allBackslashesToSlashes(file);
            editedText = front
                    + "<a href=\"file:"
                    + (fileSlashes.startsWith("//") ? "" : "//")
                    + fileSlashes
                    + "\">"
                    + file
                    + "</a>"
                    + back;
            matcher = FILE_PATTERN.matcher(editedText);
        }
        return editedText;
    }

    /** Färbt Html-Text grün ein. */
    public static String green(String text) {
        return colorText("#00A000", text);
    }

    /** Färbt Html-Text rot ein. */
    public static String red(String text) {
        return colorText("#FF0000", text);
    }

    /** Färbt Html-Text orange ein. */
    public static String orange(String text) {
        return colorText("#FF6600", text);
    }

    /** Färbt eine Zahl grün ein. */
    public static String green(int number) {
        return green(Integer.toString(number));
    }

    /** Färbt eine Zahl rot ein. */
    public static String red(int number) {
        return red(Integer.toString(number));
    }

    /** Färbt eine Zahl orange ein. */
    public static String orange(int number) {
        return orange(Integer.toString(number));
    }

    /**
     * Färbt Html-Text ein.
     *
     * @param htmlColor
     *            Farbe (z.B. "#FF0000").
     * @param text
     *            Einzufärbender Text.
     * @return Eingefärbter Text.
     */
    public static String colorText(String htmlColor, String text) {
        return "<font color=\"" + htmlColor + "\">" + text + "</font>";
    }

    /**
     * Erzeugt einen HTML-Link.
     *
     * @param url
     *            URL des Links.
     * @param linkText
     *            Beschreibung des Links im Text der HTML-Seite.
     * @return Link der Form <a href="http://www.example.org">Dies ist ein Link!</a>
     */
    public static String createLink(String url, String linkText) {
        return "<a href=\"" + url + "\">" + linkText + "</a>";
    }

    /**
     * Erstellt einen Anker.
     *
     * @param name
     *            Name des Ankers.
     * @return String der Art <a name="ankerName" />.
     */
    public static String createAnchor(String name) {
        return "<a name=\"" + name + "\" />";
    }

    /**
     * Macht den Text fett.
     *
     * @param text
     *            Text, der fett geschrieben werden soll.
     * @return Fett geschriebener Text.
     */
    public static String strong(String text) {
        return "<strong>" + text + "</strong>";
    }

    /**
     * Macht eine Zahl fett.
     *
     * @param number
     *            Zahl, die fett geschrieben werden soll.
     * @return Fett geschriebene Zahl.
     */
    public static String strong(int number) {
        return strong(Integer.toString(number));
    }

    /**
     * Erzeugt den Quelltext aus HTML hinter einer URL.
     *
     * @param url
     *            URL zum HTML-Code.
     * @return Quelltext oder Hinweis auf den Fehler beim Versuch, diesen zu Ermitteln.
     */
    public static String createContentFromUrl(URL url) {
        URLConnection conn;
        try {
            conn = url.openConnection();
        }
        catch (IOException exception) {
            exception.printStackTrace();
            return "FEHLER BEIM ÖFFNEN!!!" + exception.getMessage();
        }

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(
                conn.getInputStream(), StandardCharsets.UTF_8))) {
            String pageText = reader.lines().collect(Collectors.joining(Text.LINE_BREAK));
            return pageText;
        }
        catch (Exception exception) {
            exception.printStackTrace();
            return "FEHLER BEIM LESEN!!!" + exception.getMessage();
        }
    }

    /** Erzeugt aus dem Text einen größeren, zentrierten Text. */
    public static String createCenteredBigText(String text) {
        return "<html><center><p style=\"font-size:large\">"+ text + "</p></center></html>";
    }

    private static final Pattern SPACE_PATTERN = Pattern.compile("( {2,})");

    private static final String HTML_COMMENT_START = "<!--";
    private static final String HTML_COMMENT_END = "-->";

    /**
     * Setzt Text in Html mit Zeilenumbrüchen und Einrückungen.
     *
     * @param text
     *            Zu verändernder Text.
     * @return Veränderter Text.
     */
    public static String htmlify(String text) {
        int charactersPerLine = -1; // keine Umbrüche
        return htmlify(text, charactersPerLine);
    }

    /**
     * Setzt Text in Html mit Zeilenumbrüchen und Einrückungen.
     *
     * @param text
     *            Zu verändernder Text.
     * @param charactersPerLine
     *            Anzahl Zeilen pro Zeile.
     * @return Veränderter Text.
     */
    public static String htmlify(String text, int charactersPerLine) {
        String htmlText = htmlifyWithoutFrame(text, charactersPerLine);
        htmlText = "<html>" + htmlText  + "</html>";
        return htmlText;
    }

    /**
     * Setzt Text in Html mit Zeilenumbrüchen und Einrückungen, ohne den Text in <html>...</html>
     * zu setzen..
     *
     * @param text
     *            Zu verändernder Text.
     * @return Veränderter Text.
     */
    public static String htmlifyWithoutFrame(String text) {
        int charactersPerLine = -1; // keine Umbrüche
        return htmlifyWithoutFrame(text, charactersPerLine);
    }

    /**
     * Setzt Text in Html mit Zeilenumbrüchen und Einrückungen, ohne den Text in <html>...</html>
     * zu setzen..
     *
     * @param text
     *            Zu verändernder Text.
     * @param charactersPerLine
     *            Anzahl Zeilen pro Zeile.
     * @return Veränderter Text.
     */
    public static String htmlifyWithoutFrame(String text, int charactersPerLine) {
        String htmlText = encodeHtmlOnlyLtGt(text);
        htmlText = htmlText.replaceAll("\t", "    ");
        if (charactersPerLine > 0) {
            htmlText = Text.addLineBreaks(htmlText, charactersPerLine);
        }

        Matcher matcher = SPACE_PATTERN.matcher(htmlText);
        while (matcher.find()) {
            String spaces = matcher.group(1);
            int from = matcher.start(1);
            int to = matcher.end(1);
            String insert = Text.multipleString("&nbsp;", spaces.length());
            String start = htmlText.substring(0, from);
            String end = htmlText.substring(to);
            htmlText = start + insert + end;
            matcher = SPACE_PATTERN.matcher(htmlText);
        }

        htmlText = htmlText.replaceAll("\\r?\\n", "<br/>" + Text.LINE_BREAK);

        return htmlText;
    }

    /**
     * Bricht bereits vorhandenen HTML-Text um.
     *
     * Umbrüche finden an Leerzeichen statt.
     *
     * @param text
     *            Der zu bearbeitende HTML-Text.
     * @param charactersPerLine
     *            Anzahl der Zeichen pro Zeile.
     * @return Der umgebrochene Text.
     */
    public static String breakHtml(String text, int charactersPerLine) {
        String brokenText = text;
        brokenText = Text.addLineBreaks(brokenText, charactersPerLine);
        brokenText = brokenText.replaceAll("\\r?\\n", "<br/>" + Text.LINE_BREAK);
        return brokenText;
    }

    /**
     * Tauscht &lt; gegen < und &gt; gegen > aus. (&amp;lt; wird zu <)
     *
     * @param text
     *            zu bearbeitender Text.
     * @return bearbeiteter Text.
     */
    public static String decodeHtmlOnlyLtGt(String text) {
        String editedText = text;

        while (editedText.contains("&lt;")) {
            int pos = editedText.indexOf("&lt;");
            if (editedText.length() > pos + 4) {
                editedText = editedText.substring(0, pos) + "<" + editedText.substring(pos + 4);
            }
            else {
                editedText = editedText.substring(0, pos) + "<";
            }
        }
        while (editedText.contains("&gt;")) {
            int pos = editedText.indexOf("&gt;");
            if (editedText.length() > pos + 4) {
                editedText = editedText.substring(0, pos) + ">" + editedText.substring(pos + 4);
            }
            else {
                editedText = editedText.substring(0, pos) + ">";
            }
        }

        return editedText;
    }

    /**
     * Tauscht < gegen &lt; und > gegen &gt; aus. (< wird zu &amp;lt;)
     *
     * @param text
     *            zu bearbeitender Text.
     * @return bearbeiteter Text.
     */
    public static String encodeHtmlOnlyLtGt(String text) {
        String editedText = text;

        while (editedText.contains("<")) {
            int pos = editedText.indexOf("<");
            if (editedText.length() > pos + 1) {
                editedText = editedText.substring(0, pos) + "&lt;" + editedText.substring(pos + 1);
            }
            else {
                editedText = editedText.substring(0, pos) + "&lt;";
            }
        }
        while (editedText.contains(">")) {
            int pos = editedText.indexOf(">");
            if (editedText.length() > pos + 1) {
                editedText = editedText.substring(0, pos) + "&gt;" + editedText.substring(pos + 1);
            }
            else {
                editedText = editedText.substring(0, pos) + "&gt;";
            }
        }

        return editedText;
    }

    /**
     * Tauscht &amp; gegen &, &lt; gegen < und &gt; gegen > aus. (&amp;lt; wird zu <)
     *
     * @param text
     *            zu bearbeitender Text.
     * @return bearbeiteter Text.
     */
    public static String decodeHtmlAmpLtAndGt(String text) {
        String editedText = text;

        editedText = editedText.replace("&amp;", "&");
        editedText = editedText.replace("&lt;", "<");
        editedText = editedText.replace("&gt;", ">");

        return editedText;
    }

    /**
     * Tauscht & gegen &amp;, < gegen &lt; und > gegen &gt; aus. (< wird zu &amp;lt;)
     *
     * @param text
     *            zu bearbeitender Text.
     * @return bearbeiteter Text.
     */
    public static String encodeHtmlAmpLtAndGt(String text) {
        String editedText = text;

        editedText = editedText.replace("&", "&amp;");
        editedText = editedText.replace("<", "&lt;");
        editedText = editedText.replace(">", "&gt;");

        return editedText;
    }

    /**
     * Erstellt eine Liste von Ersetzungen von UTF-8-Zeichen und ihrer Entsprechung im HTML-Code.
     *
     * @see https://wiki.selfhtml.org/wiki/Referenz:HTML/Zeichenreferenz
     */
    private static List<Pair<String>> createHtmlEncodingList() {
        List<Pair<String>> list = new ArrayList<>();

        list.add(new Pair<String>("&amp;", "&")); // ZUERST!

        /* HTML-eigene Zeichen: */
        list.add(new Pair<String>("&quot;", "\""));
        // list.add(new Pair<String>("&amp;", "&")); // Dies muss zuerst erfolgen, sonst wird aus "
                                                     // erst &quot; und dann &amp;quot;
        list.add(new Pair<String>("&lt;", "<"));
        list.add(new Pair<String>("&gt;", ">"));
        list.add(new Pair<String>("&apos;", "'"));

        /* Latin1-Ergänzung: */
        list.add(new Pair<String>("&nbsp;", " ")); // dies möchte man oft nicht bei Text -> Html
                                                   // Siehe encodeHtmlWithoutSpaces()
        list.add(new Pair<String>("&iexcl;", "¡"));
        list.add(new Pair<String>("&cent;", "¢"));
        list.add(new Pair<String>("&pound;", "£"));
        list.add(new Pair<String>("&curren;", "¤"));
        list.add(new Pair<String>("&yen;", "¥"));
        list.add(new Pair<String>("&brvbar;", "¦"));
        list.add(new Pair<String>("&sect;", "§"));
        list.add(new Pair<String>("&uml;", "¨"));
        list.add(new Pair<String>("&copy;", "©"));
        list.add(new Pair<String>("&ordf;", "ª"));
        list.add(new Pair<String>("&laquo;", "«"));
        list.add(new Pair<String>("&not;", "¬"));
        list.add(new Pair<String>("&reg;", "®"));
        list.add(new Pair<String>("&macr;", "¯"));
        list.add(new Pair<String>("&deg;", "°"));
        list.add(new Pair<String>("&plusmn;", "±"));
        list.add(new Pair<String>("&sup2;", "²"));
        list.add(new Pair<String>("&sup3;", "³"));
        list.add(new Pair<String>("&acute;", "´"));
        list.add(new Pair<String>("&micro;", "µ"));
        list.add(new Pair<String>("&para;", "¶"));
        list.add(new Pair<String>("&middot;", "·"));
        list.add(new Pair<String>("&cedil;", "¸"));
        list.add(new Pair<String>("&sup1;", "¹"));
        list.add(new Pair<String>("&ordm;", "º"));
        list.add(new Pair<String>("&raquo;", " »"));
        list.add(new Pair<String>("&frac14;", "¼"));
        list.add(new Pair<String>("&frac12;", "½"));
        list.add(new Pair<String>("&frac34;", "¾"));
        list.add(new Pair<String>("&iquest;", "¿"));
        list.add(new Pair<String>("&Agrave;", "À"));
        list.add(new Pair<String>("&Aacute;", "Á"));
        list.add(new Pair<String>("&Acirc;", "Â"));
        list.add(new Pair<String>("&Atilde;", "Ã"));
        list.add(new Pair<String>("&Auml;", "Ä"));
        list.add(new Pair<String>("&Aring;", "Å"));
        list.add(new Pair<String>("&AElig;", "Æ"));
        list.add(new Pair<String>("&Ccedil;", "Ç"));
        list.add(new Pair<String>("&Egrave;", "È"));
        list.add(new Pair<String>("&Eacute;", "É"));
        list.add(new Pair<String>("&Ecirc;", "Ê"));
        list.add(new Pair<String>("&Euml;", "Ë"));
        list.add(new Pair<String>("&Igrave;", "Ì"));
        list.add(new Pair<String>("&Iacute;", "Í"));
        list.add(new Pair<String>("&Icirc;", "Î"));
        list.add(new Pair<String>("&Iuml;", "Ï"));
        list.add(new Pair<String>("&ETH;", "Ð"));
        list.add(new Pair<String>("&Ntilde;", "Ñ"));
        list.add(new Pair<String>("&Ograve;", "Ò"));
        list.add(new Pair<String>("&Oacute;", "Ó"));
        list.add(new Pair<String>("&Ocirc;", "Ô"));
        list.add(new Pair<String>("&Otilde;", "Õ"));
        list.add(new Pair<String>("&Ouml;", "Ö"));
        list.add(new Pair<String>("&times;", "×"));
        list.add(new Pair<String>("&Oslash;", "Ø"));
        list.add(new Pair<String>("&Ugrave;", "Ù"));
        list.add(new Pair<String>("&Uacute;", "Ú"));
        list.add(new Pair<String>("&Ucirc;", "Û"));
        list.add(new Pair<String>("&Uuml;", "Ü"));
        list.add(new Pair<String>("&Yacute;", "Ý"));
        list.add(new Pair<String>("&THORN;", "Þ"));
        list.add(new Pair<String>("&szlig;", "ß"));
        list.add(new Pair<String>("&agrave;", "à"));
        list.add(new Pair<String>("&aacute;", "á"));
        list.add(new Pair<String>("&acirc;", "â"));
        list.add(new Pair<String>("&atilde;", "ã"));
        list.add(new Pair<String>("&auml;", "ä"));
        list.add(new Pair<String>("&aring;", "å"));
        list.add(new Pair<String>("&aelig;", "æ"));
        list.add(new Pair<String>("&ccedil;", "ç"));
        list.add(new Pair<String>("&egrave;", "è"));
        list.add(new Pair<String>("&eacute;", "é"));
        list.add(new Pair<String>("&ecirc;", "ê"));
        list.add(new Pair<String>("&euml;", "ë"));
        list.add(new Pair<String>("&igrave;", "ì"));
        list.add(new Pair<String>("&iacute;", "í"));
        list.add(new Pair<String>("&icirc;", "î"));
        list.add(new Pair<String>("&iuml;", "ï"));
        list.add(new Pair<String>("&eth;", "ð"));
        list.add(new Pair<String>("&ntilde;", "ñ"));
        list.add(new Pair<String>("&ograve;", "ò"));
        list.add(new Pair<String>("&oacute;", "ó"));
        list.add(new Pair<String>("&ocirc;", "ô"));
        list.add(new Pair<String>("&otilde;", "õ"));
        list.add(new Pair<String>("&ouml;", "ö"));
        list.add(new Pair<String>("&divide;", "÷"));
        list.add(new Pair<String>("&oslash;", "ø"));
        list.add(new Pair<String>("&ugrave;", "ù"));
        list.add(new Pair<String>("&uacute;", "ú"));
        list.add(new Pair<String>("&ucirc;", "û"));
        list.add(new Pair<String>("&uuml;", "ü"));
        list.add(new Pair<String>("&yacute;", "ý"));
        list.add(new Pair<String>("&thorn;", "þ"));
        list.add(new Pair<String>("&yuml;", "ÿ"));

        /* Lateinisch erweitert: */
        list.add(new Pair<String>("&OElig;", "Œ"));
        list.add(new Pair<String>("&oelig;", "œ"));
        list.add(new Pair<String>("&Scaron;", "Š"));
        list.add(new Pair<String>("&scaron;", "š"));
        list.add(new Pair<String>("&Yuml;", "Ÿ"));
        list.add(new Pair<String>("&fnof;", "ƒ"));

        /* Diakritische Zeichen: */
        list.add(new Pair<String>("&circ;", "ˆ"));
        list.add(new Pair<String>("&tilde;", "˜"));

        /* Griechische Buchstaben: */
        list.add(new Pair<String>("&Alpha;", "Α"));
        list.add(new Pair<String>("&alpha;", "α"));
        list.add(new Pair<String>("&Beta;", "Β"));
        list.add(new Pair<String>("&beta;", "β"));
        list.add(new Pair<String>("&Gamma;", "Γ"));
        list.add(new Pair<String>("&gamma;", "γ"));
        list.add(new Pair<String>("&Delta;", "Δ"));
        list.add(new Pair<String>("&delta;", "δ"));
        list.add(new Pair<String>("&Epsilon;", "Ε"));
        list.add(new Pair<String>("&epsilon;", "ε"));
        list.add(new Pair<String>("&Zeta;", "Ζ"));
        list.add(new Pair<String>("&zeta;", "ζ"));
        list.add(new Pair<String>("&Eta;", "Η"));
        list.add(new Pair<String>("&eta;", "η"));
        list.add(new Pair<String>("&Theta;", "Θ"));
        list.add(new Pair<String>("&theta;", "θ"));
        list.add(new Pair<String>("&Iota;", "Ι"));
        list.add(new Pair<String>("&iota;", "ι"));
        list.add(new Pair<String>("&Kappa;", "Κ"));
        list.add(new Pair<String>("&kappa;", "κ"));
        list.add(new Pair<String>("&Lambda;", "Λ"));
        list.add(new Pair<String>("&lambda;", "λ"));
        list.add(new Pair<String>("&Mu;", "Μ"));
        list.add(new Pair<String>("&mu;", "μ"));
        list.add(new Pair<String>("&Nu;", "Ν"));
        list.add(new Pair<String>("&nu;", "ν"));
        list.add(new Pair<String>("&Xi;", "Ξ"));
        list.add(new Pair<String>("&xi;", "ξ"));
        list.add(new Pair<String>("&Omicron;", "Ο"));
        list.add(new Pair<String>("&omicron;", "ο"));
        list.add(new Pair<String>("&Pi;", "Π"));
        list.add(new Pair<String>("&pi;", "π"));
        list.add(new Pair<String>("&Rho;", "Ρ"));
        list.add(new Pair<String>("&rho;", "ρ"));
        list.add(new Pair<String>("&Sigma;", "Σ"));
        list.add(new Pair<String>("&sigmaf;", "ς"));
        list.add(new Pair<String>("&sigma;", "σ"));
        list.add(new Pair<String>("&Tau;", "Τ"));
        list.add(new Pair<String>("&tau;", "τ"));
        list.add(new Pair<String>("&Upsilon;", "Υ"));
        list.add(new Pair<String>("&upsilon;", "υ"));
        list.add(new Pair<String>("&Phi;", "Φ"));
        list.add(new Pair<String>("&phi;", "φ"));
        list.add(new Pair<String>("&Chi;", "Χ"));
        list.add(new Pair<String>("&chi;", "χ"));
        list.add(new Pair<String>("&Psi;", "Ψ"));
        list.add(new Pair<String>("&psi;", "ψ"));
        list.add(new Pair<String>("&Omega;", "Ω"));
        list.add(new Pair<String>("&omega;", "ω"));
        list.add(new Pair<String>("&thetasym;", "ϑ"));
        list.add(new Pair<String>("&upsih;", "ϒ"));
        list.add(new Pair<String>("&piv;", "ϖ"));

        /* Mathematische Symbole: */
        list.add(new Pair<String>("&forall;", "∀"));
        list.add(new Pair<String>("&part;", "∂"));
        list.add(new Pair<String>("&exist;", "∃"));
        list.add(new Pair<String>("&empty;", "∅"));
        list.add(new Pair<String>("&nabla;", "∇"));
        list.add(new Pair<String>("&isin;", "∈"));
        list.add(new Pair<String>("&notin;", "∉"));
        list.add(new Pair<String>("&ni;", "∋"));
        list.add(new Pair<String>("&prod;", "∏"));
        list.add(new Pair<String>("&sum;", "∑"));
        list.add(new Pair<String>("&minus;", "−"));
        list.add(new Pair<String>("&lowast;", "∗"));
        list.add(new Pair<String>("&radic;", "√"));
        list.add(new Pair<String>("&prop;", "∝"));
        list.add(new Pair<String>("&infin;", "∞"));
        list.add(new Pair<String>("&ang;", "∠"));
        list.add(new Pair<String>("&and;", "∧"));
        list.add(new Pair<String>("&or;", "∨"));
        list.add(new Pair<String>("&cap;", "∩"));
        list.add(new Pair<String>("&cup;", "∪"));
        list.add(new Pair<String>("&int;", "∫"));
        list.add(new Pair<String>("&there4;", "∴"));
        list.add(new Pair<String>("&sim;", "∼"));
        list.add(new Pair<String>("&cong;", "≅"));
        list.add(new Pair<String>("&asymp;", "≈"));
        list.add(new Pair<String>("&ne;", "≠"));
        list.add(new Pair<String>("&equiv;", "≡"));
        list.add(new Pair<String>("&le;", "≤"));
        list.add(new Pair<String>("&ge;", "≥"));
        list.add(new Pair<String>("&sub;", "⊂"));
        list.add(new Pair<String>("&sup;", "⊃"));
        list.add(new Pair<String>("&nsub;", "⊄"));
        list.add(new Pair<String>("&sube;", "⊆"));
        list.add(new Pair<String>("&supe;", "⊇"));
        list.add(new Pair<String>("&oplus;", "⊕"));
        list.add(new Pair<String>("&otimes;", "⊗"));
        list.add(new Pair<String>("&perp;", "⊥"));
        list.add(new Pair<String>("&sdot;", "⋅"));
        list.add(new Pair<String>("&loz;", "◊"));

        /* Technische Symbole: */
        list.add(new Pair<String>("&lang;", "〈"));
        list.add(new Pair<String>("&rang;", "〉"));

        /* Pfeil-Symbole: */
        list.add(new Pair<String>("&larr;", "←"));
        list.add(new Pair<String>("&uarr;", "↑"));
        list.add(new Pair<String>("&rarr;", "→"));
        list.add(new Pair<String>("&darr;", "↓"));
        list.add(new Pair<String>("&harr;", "↔"));
        list.add(new Pair<String>("&crarr;", "↵"));
        list.add(new Pair<String>("&lArr;", "⇐"));
        list.add(new Pair<String>("&uArr;", "⇑"));
        list.add(new Pair<String>("&rArr;", "⇒"));
        list.add(new Pair<String>("&dArr;", "⇓"));
        list.add(new Pair<String>("&hArr;", "⇔"));

        /* diverse Symbole: */
        list.add(new Pair<String>("&bull;", "•"));
        list.add(new Pair<String>("&prime;", "′"));
        list.add(new Pair<String>("&Prime;", "″"));
        list.add(new Pair<String>("&oline;", "‾"));
        list.add(new Pair<String>("&frasl;", "⁄"));
        list.add(new Pair<String>("&weierp;", "℘"));
        list.add(new Pair<String>("&image;", "ℑ"));
        list.add(new Pair<String>("&real;", "ℜ"));
        list.add(new Pair<String>("&trade;", "™"));
        list.add(new Pair<String>("&euro;", "€"));
        list.add(new Pair<String>("&alefsym;", "ℵ"));
        list.add(new Pair<String>("&spades;", "♠"));
        list.add(new Pair<String>("&clubs;", "♣"));
        list.add(new Pair<String>("&hearts;", "♥"));
        list.add(new Pair<String>("&diams;", "♦"));

        /* Interpunktionszeichen: */
        list.add(new Pair<String>("&ensp;", " "));
        list.add(new Pair<String>("&emsp;", " "));
        list.add(new Pair<String>("&thinsp;", " "));
        list.add(new Pair<String>("-", "-"));
        list.add(new Pair<String>("&zwnj;", " ‌"));
        list.add(new Pair<String>("&zwj;", " ‍"));
        list.add(new Pair<String>("&lrm;", "‎"));
        list.add(new Pair<String>("&rlm;", "‏"));
        list.add(new Pair<String>("&ndash;", "–"));
        list.add(new Pair<String>("&mdash;", "—"));
        list.add(new Pair<String>("&lsquo;", "‘"));
        list.add(new Pair<String>("&rsquo;", "’"));
        list.add(new Pair<String>("&sbquo;", "‚"));
        list.add(new Pair<String>("&ldquo;", "“"));
        list.add(new Pair<String>("&rdquo;", "”"));
        list.add(new Pair<String>("&bdquo;", "„"));
        list.add(new Pair<String>("&dagger;", "†"));
        list.add(new Pair<String>("&Dagger;", "‡"));
        list.add(new Pair<String>("&hellip;", "…"));
        list.add(new Pair<String>("&permil;", "‰"));
        list.add(new Pair<String>("&lsaquo;", "‹"));
        list.add(new Pair<String>("&rsaquo;", "›"));

        /* hochgestellte Ziffern: */
        list.add(new Pair<String>("&sup1;", "¹"));
        list.add(new Pair<String>("&sup2;", "²"));
        list.add(new Pair<String>("&sup3;", "³"));

        return list;
    }

    /**
     * Ersetzt bestimmte UTF-8 Zeichen durch HTML-Codes in einem Text.
     *
     * @param text
     *            Zu bearbeitender HTML-Text.
     * @return Text mit entfernten HTML-Zeichen.
     */
    public static String encodeHtml(String text) {
        String editedText = text;

        for (Pair<String> htmlToUtf8 : HTML_TO_UTF8) {
            String html = htmlToUtf8.getFirst();
            String utf8 = htmlToUtf8.getSecond();
            editedText = editedText.replace(utf8, html);
        }

        return editedText;
    }

    /**
     * Ersetzt bestimmte UTF-8 Zeichen durch HTML-Codes in einem Text. Das Leerzeichen " " wird
     * hierbei aber nicht in "&nbsp;" umgesetzt.
     *
     * @param text
     *            Zu bearbeitender HTML-Text.
     * @return Text mit entfernten HTML-Zeichen.
     */
    public static String encodeHtmlWithoutSpaces(String text) {
        String editedText = text;

        for (Pair<String> htmlToUtf8 : HTML_TO_UTF8) {
            String html = htmlToUtf8.getFirst();
            String utf8 = htmlToUtf8.getSecond();
            if (!" ".equals(utf8)) {
                editedText = editedText.replace(utf8, html);
            }
        }

        return editedText;
    }

    /**
     * Ersetzt bestimmte HTML-Codes durch UTF-8 Zeichen in einem Text.
     *
     * @param text
     *            Zu bearbeitender HTML-Text.
     * @return Text mit entfernten HTML-Zeichen.
     */
    public static String decodeHtml(String text) {
        String editedText = text;

        for (Pair<String> htmlToUtf8 : HTML_TO_UTF8) {
            String html = htmlToUtf8.getFirst();
            String utf8 = htmlToUtf8.getSecond();
            editedText = editedText.replace(html, utf8);
        }

        return editedText;
    }

    /** Umgibt die Eingabe mit einem blockquote-Tag. */
    public static String blockquote(String input) {
        return "<blockquote>" + input + "</blockquote>";
    }

    /** Umgibt die Eingabe mit einem tt-Tag. */
    public static String tt(String input) {
        return "<tt>" + input + "</tt>";
    }

    /** Umgibt die Eingabe mit einem tt- und einem blockquote-Tag. */
    public static String blockquoteTt(String input) {
        return blockquote(tt(input));
    }

    /** Entfernt aus dem Html-Inhalt alle Kommentare. */
    public static String removeHtmlComments(String content) { // TODO siehe removeComments()
        String cleanedContent = content;
        boolean goOn = true;
        while (goOn) {
            goOn = false;
            int openIndex = cleanedContent.indexOf(HTML_COMMENT_START);
            if (-1 != openIndex) {
                int firstSearchIndex = openIndex + HTML_COMMENT_START.length();
                int closeIndex = cleanedContent.indexOf(HTML_COMMENT_END, firstSearchIndex);
                if (-1 != closeIndex) {
                    String front = cleanedContent.substring(0, openIndex);
                    String rear = cleanedContent.substring(closeIndex + HTML_COMMENT_END.length());
                    cleanedContent = front + rear;
                    goOn = true;
                }
            }
        }
        return cleanedContent;
    }

    /**
     * Füllt einen String hinten bis zur angegebenen Gesamtlänge mit Leerzeichen auf.
     *
     * fillWithSpaces("abc", 5) ergibt den String "abc"&nbsp;"&nbsp;".
     *
     * Ist der Text mindestens so lang wie die angegebene Länge, wird er unverändert zurückgegeben.
     *
     * @param text
     *            Aufzufüllender Text.
     * @param length
     *            Länge, bis zu der der Text mit Leerzeichen aufzufüllen ist.
     * @return Aufgefüllter Text.
     */
    public static String fillWithSpaces(String text, int length) {
        if (length <= text.length()) {
            return text;
        }
        else {
            String fill = Text.multipleString("&nbsp;", length - text.length());
            return text + fill;
        }
    }

    /**
     * Füllt einen String vorn bis zur angegebenen Gesamtlänge mit Leerzeichen auf.
     *
     * fillWithSpacesAtFront("abc", 5) ergibt den String ""&nbsp;"&nbsp;abc".
     *
     * Ist der Text mindestens so lang wie die angegebene Länge, wird er unverändert zurückgegeben.
     *
     * @param text
     *            Aufzufüllender Text.
     * @param length
     *            Länge, bis zu der der Text mit Leerzeichen aufzufüllen ist.
     * @return Aufgefüllter Text.
     */
    public static String fillWithSpacesAtFront(String text, int length) {
        if (length <= text.length()) {
            return text;
        }
        else {
            String fill = Text.multipleString("&nbsp;", length - text.length());
            return fill + text;
        }
    }

    /** Entfernt alle Links aus einem HTML-Dokument. */
    public static String removeLinks(String content) {
        return content.replaceAll("<a(?: +| *\n *)href(?: *| *\n *)=[^<>]+>[^<>]+</a>", "");
    }

    /** Entfernt alle Links aus einem HTML-Dokument. */
    public static String removeLinksSecure(String html) {
        List<String> starts = CollectionsHelper.buildListFrom("<a ", "<A ", "<a\n", "<A\n",
                "<a\r\n", "<A\r\n", "<a" + Text.LINE_BREAK, "<A" + Text.LINE_BREAK);
        List<String> ends = CollectionsHelper.buildListFrom("</a>", "</A>");
        return Text.removeAllPartsWithStartsAndEnds(html, starts, ends);
    }

    /** Entfernt alle Links aus einem HTML-Dokument. */
    public static String removeLinksSecureButKeepLinkDescription(String html) {
        List<String> starts = CollectionsHelper.buildListFrom("<a ", "<A ", "<a\n", "<A\n",
                "<a\r\n", "<A\r\n", "<a" + Text.LINE_BREAK, "<A" + Text.LINE_BREAK);
        List<String> ends = CollectionsHelper.buildListFrom("</a>", "</A>");

        String changed = html;

        boolean searchForLinks = true;

        while (searchForLinks) {
            FoundSearch foundStart = Text.findFirstPosition(changed, starts);
            int startIndex = foundStart.getIndex();
            if (startIndex == -1) {
                searchForLinks = false;
            }
            else {
                String start = foundStart.getSearch();
                FoundSearch foundEnd = Text.findFirstPosition(changed, ends,
                        startIndex + start.length());
                int endIndex = foundEnd.getIndex();
                if (endIndex == -1) {
                    changed = changed.substring(0, startIndex);
                }
                else {
                    String end = foundEnd.getSearch();
                    String fullLinkWithOutEnd = changed.substring(startIndex, endIndex);
                    int lastClosingBraceIndex = fullLinkWithOutEnd.lastIndexOf(">");
                    String linkDescription;
                    if (lastClosingBraceIndex == -1) {
                        linkDescription = "";
                    }
                    else {
                        linkDescription = fullLinkWithOutEnd.substring(lastClosingBraceIndex + 1);
                    }
                    changed = changed.substring(0, startIndex)
                            + linkDescription
                            + changed.substring(endIndex + end.length());
                }
            }
        }

        return changed;
    }

    /** Prüft ob der übergebene String ein Keyword von HTML5 ist. */
    public static boolean isHtml5KeyWord(String input) {
        String lowerCaseInput = input.toLowerCase();
        return HtmlTool.HTML5_KEY_WORDS.contains(lowerCaseInput);
    }

    /** Entfernt alle Kommentare aus dem HTML. */
    public static String removeComments(String html) { // TODO siehe removeHtmlComments
        String commentStart = "<!--";
        String commentEnd = "-->";
        return Text.removeAllPartsWithStartAndEnd(html, commentStart, commentEnd);
    }

    /**
     * Entfernt alle Skripte aus dem HTML.
     *
     * Jede Kombination von sCRipT können wir nicht abfangen... ggf. vorher lowerTagNames()
     * aufrufen!
     */
    public static String removeScripts(String html) {
        List<String> starts = HtmlTool.createUpperAndLowerCaseHtmlTagStarts("script");
        List<String> ends = HtmlTool.createUpperAndLowerCaseHtmlEndingTags("script");
        return Text.removeAllPartsWithStartsAndEnds(html, starts, ends);
    }

    /**
     * Entfernt alle Skripte aus dem HTML.
     *
     * Jede Kombination von iMg können wir nicht abfangen... ggf. vorher lowerTagNames()
     * aufrufen!
     */
    public static String removeImages(String html) {
        List<String> starts = HtmlTool.createUpperAndLowerCaseHtmlTagStarts("img");
        List<String> ends = CollectionsHelper.buildListFrom("/>", ">");
        return Text.removeAllPartsWithStartsAndEnds(html, starts, ends);
    }

    /**
     * Entfernt Definitionslisten der Form <dl> ... </dl> aus dem übergebenen Abschnitt von HTML.
     *
     * @param html Zeilen mit HTML-Code.
     * @return Bereinigter HTML-Code
     */
    public static String removeDefinitionLists(String html) {
        List<String> starts = HtmlTool.createUpperAndLowerCaseHtmlTagStarts("dl");
        List<String> ends = HtmlTool.createUpperAndLowerCaseHtmlEndingTags("dl");
        return Text.removeAllPartsWithStartsAndEnds(html, starts, ends);
    }

    static List<String> createUpperAndLowerCaseHtmlTagStarts(String tag) {
        String lowerTag = Text.toLowerCase(tag);
        String upperTag = Text.toUpperCase(tag);
        return CollectionsHelper.buildListFrom(
                "<" + lowerTag + ">", "<" + upperTag + ">",
                "<" + lowerTag + " ", "<" + upperTag + " ",
                "<" + lowerTag + "\n", "<" + upperTag + "\n",
                "<" + lowerTag + "\r\n", "<" + upperTag + "\r\n",
                "<" + lowerTag + Text.LINE_BREAK, "<" + upperTag + Text.LINE_BREAK);
    }

    static List<String> createUpperAndLowerCaseHtmlEndingTags(String tag) {
        String lowerTag = Text.toLowerCase(tag);
        String upperTag = Text.toUpperCase(tag);
        return CollectionsHelper.buildListFrom("</" + lowerTag + ">", "</" + upperTag + ">");
    }

   /** Wandelt alle öffnenden und schließenden HTML5-Tags in Kleinbuchstaben. */
    public static String lowerTagNames(String html) {
        String changed = html;

        for (String keyword : HTML5_KEY_WORDS) {
            changed = changed.replaceAll("(?i)<" + keyword, "<" + keyword);
            changed = changed.replaceAll("(?i)</" + keyword, "</" + keyword);
        }

        return changed;
    }

    /** Entfernt jegliches Markup aus dem übergebenen HTML-Text. */
    public static String removeHtmlMarkup(String html) {
        return html.replaceAll("</?[^>]+>", "");
    }

}
