OS2.jugend: Unterschied zwischen den Versionen

Aus Online-Soccer-Wiki
Zur Navigation springen Zur Suche springen
(Update Version 0.50 (Class, define, setConst, Logging, Speicher umgebaut, FreeValue/SelValue/AutoReset, Menü-Optionen interaktiv abfragen, viel zusätzliche Spalten))
(Update Version 0.73 (Bugfix U18-Jahrgang in 1. Liga, neue U19) vom 24.08.2020)
 
(10 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
[[Kategorie:Greasemonkey]]
[[Kategorie:Greasemonkey]]
[[Kategorie:Greasemonkey WE]]
{| style="background-color:white; font-size:11px; float: right; margin:3px 3px 3px 10px; border:1px solid #999; border-color: #9C1818; border-collapse:collapse;" width=500 cellpadding=3 cellspacing=0
{| style="background-color:white; font-size:11px; float: right; margin:3px 3px 3px 10px; border:1px solid #999; border-color: #9C1818; border-collapse:collapse;" width=500 cellpadding=3 cellspacing=0
| colspan="2" style="padding:0.3em; background-color:#9C1818; font-size: 18px; color:#FFFFFF" align=center| '''OS2.jugend'''
| colspan="2" style="padding:0.3em; background-color:#9C1818; font-size: 18px; color:#FFFFFF" align=center| '''OS2.jugend'''
Zeile 7: Zeile 8:
|- bgcolor="#FFCC00"
|- bgcolor="#FFCC00"
| '''Version'''
| '''Version'''
| '''0.50'''
| '''0.73 (WebExtensions)'''
|- bgcolor="#FFCC00"
|- bgcolor="#FFCC00"
| '''Autor'''
| '''Autor'''
Zeile 26: Zeile 27:
| '''ju.php?page=2'''
| '''ju.php?page=2'''
| '''Jugendteam-Spielereinzelwerte'''
| '''Jugendteam-Spielereinzelwerte'''
|-
| '''ju.php?page=3'''
| '''Jugendteam-Opt. Skill'''
|-
| '''ju.php?page=4'''
| '''Jugendteam-Optionen'''
|}
|}
|- bgcolor="#FFCC00"
|- bgcolor="#FFCC00"
| '''Funktionalität'''
| '''Funktionalität'''
| '''Trennstriche zwischen den Jahrgängen'''<br> '''Aktueller Skill, Opti und MW'''<br> '''Prognose von Opti und MW für Ende Jahrgang 18'''<br> '''Optionen und Menu'''<br> '''Neue Marktwertformel'''<br> '''Automatische Ermittlung des ZATs'''<br> '''Hidden-Optionen und Datenspeicher'''<br> '''Geburtstage und dezimales Alter'''<br> '''Erweiterte Optionen auch auf der Seite'''<br> '''Zusatzspalten Talent/Quote/Aufw./Geb./Alter'''<br> '''Zusatzspalten Quote/Alter/Pos in der Übersicht'''<br> '''Zusatzspalten Alter ersetzen/Aufwertungen kurz+TOR'''<br> '''Zusatzspalten fix/tr./%H/%N/Prios jetzt und Ende'''<br> '''Interaktive Menü-Optionen'''<br> '''Gemeinsame Code- und Datenbasis'''
| '''Trennstriche zwischen den Jahrgängen'''<br> '''Aktueller Skill, Opti und MW'''<br> '''Prognose von Opti und MW für Ende Jahrgang 18'''<br> '''Optionen und Menu'''<br> '''Neue Marktwertformel'''<br> '''Automatische Ermittlung des ZATs'''<br> '''Hidden-Optionen und Datenspeicher'''<br> '''Geburtstage und dezimales Alter'''<br> '''Erweiterte Optionen auch auf der Seite'''<br> '''Zusatzspalten Talent/Quote/Aufw./Geb./Alter'''<br> '''Zusatzspalten Quote/Alter/Pos in der Übersicht'''<br> '''Zusatzspalten Alter ersetzen/Aufwertungen kurz+TOR'''<br> '''Zusatzspalten fix/tr./%H/%N/Prios jetzt und Ende'''<br> '''Interaktive Menü-Optionen'''<br> '''Gemeinsame Code- und Datenbasis'''<br> '''Qualitätsbalken'''<br> '''Markierung der Primärskills bei Einzelwerten und Aufwertungen'''<br> '''Beachtung von Jugendförderung und Doppelpositionen'''<br> '''Warnung vor Ende 18 in letzter Periode und mehr am letzten ZAT'''<br> '''Reguläre Ausdrücke im @include'''<br> '''Neues Design und Seite "Opt. Skill"'''<br> '''Gruppierung nach Jahrgängen U13 bis U18 (per Option)'''<br> '''Warnung vor Ende 18 auch im Managerbüro (per Option)'''<br> '''Warnung vor Sperre des Ziehens im Falle eines Aufstiegs'''<br> '''Neuer Jahrgang U19'''
 
|- bgcolor="#FFCC00"
|- bgcolor="#FFCC00"
| '''Letzte Änderung'''
| '''Letzte Änderung'''
| '''{{REVISIONDAY}}.{{REVISIONMONTH}}.{{REVISIONYEAR}}'''
| '''{{REVISIONDAY}}.{{REVISIONMONTH}}.{{REVISIONYEAR}}'''
|}
|}
== [https://github.com/Eselce/OS2.scripts/blob/master/OS2.jugend.user.js Quellcode] [https://eselce.github.io/OS2.scripts/OS2.jugend.user.js INSTALLATION] ==
<pre>
<pre>
// ==UserScript==
// ==UserScript==
// @name        OS2.jugend
// @name        OS2.jugend
// @namespace    http://os.ongapo.com/
// @namespace    http://os.ongapo.com/
// @version      0.50
// @version      0.73
// @copyright    2013+
// @copyright    2013+
// @author      Andreas Eckes (Strindheim BK) / Sven Loges (SLC)
// @author      Sven Loges (SLC) / Andreas Eckes (Strindheim BK)
// @description  Jugendteam-Script fuer Online Soccer 2.0
// @description  Jugendteam-Script fuer Online Soccer 2.0
// @include      http*://os.ongapo.com/haupt.php
// @include      /^https?://(www\.)?(os\.ongapo\.com|online-soccer\.eu|os-zeitungen\.com)/haupt\.php(\?changetosecond=\w+(&\S+)*)?$/
// @include      http*://os.ongapo.com/haupt.php?changetosecond=*
// @include      /^https?://(www\.)?(os\.ongapo\.com|online-soccer\.eu|os-zeitungen\.com)/ju\.php(\?page=\d+(&\S+)*)?$/
// @include      http*://os.ongapo.com/ju.php
// @grant        GM.getValue
// @include      http*://os.ongapo.com/ju.php?page=*
// @grant        GM.setValue
// @include      http*://www.os.ongapo.com/haupt.php
// @grant        GM.deleteValue
// @include      http*://www.os.ongapo.com/haupt.php?changetosecond=*
// @grant        GM.registerMenuCommand
// @include      http*://www.os.ongapo.com/ju.php
// @grant        GM.info
// @include      http*://www.os.ongapo.com/ju.php?page=*
// @require     https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @include      http*://online-soccer.eu/haupt.php
// @include      http*://online-soccer.eu/haupt.php?changetosecond=*
// @include      http*://online-soccer.eu/ju.php
// @include      http*://online-soccer.eu/ju.php?page=*
// @include      http*://www.online-soccer.eu/haupt.php
// @include     http*://www.online-soccer.eu/haupt.php?changetosecond=*
// @include      http*://www.online-soccer.eu/ju.php
// @include      http*://www.online-soccer.eu/ju.php?page=*
// @grant        GM_getValue
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_setValue
Zeile 65: Zeile 67:
// ==/UserScript==
// ==/UserScript==


// ECMAScript 6: Erlaubt 'const', 'let', ...
// ECMAScript 6:
/* jshint esnext: true */
/* jshint esnext: true */
/* jshint moz: true */
/* jshint moz: true */
Zeile 112: Zeile 114:
// Moegliche Optionen (hier die Standardwerte editieren oder ueber das Benutzermenu setzen):
// Moegliche Optionen (hier die Standardwerte editieren oder ueber das Benutzermenu setzen):
const __OPTCONFIG = {
const __OPTCONFIG = {
     'zeigeTal' : {       // Spaltenauswahl fuer Talente (true = anzeigen, false = nicht anzeigen)
     'ersetzeSkills' : {   // Auswahl fuer prognostizierte Einzelskills mit Ende 18 statt der aktuellen (true = Ende 18, false = aktuell)
                   'Name'      : "showTclasses",
                   'Name'      : "substSkills",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : false,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Talent ein",
                   'Label'    : "Einzelwerte Ende",
                   'Hotkey'    : 'T',
                   'Hotkey'    : 'E',
                   'AltLabel'  : "Talent aus",
                   'AltLabel'  : "Einzelwerte aktuell",
                   'AltHotkey' : 'T',
                   'AltHotkey' : 'k',
                   'FormLabel' : "Talent"
                   'FormLabel' : "Prognose Einzelwerte"
               },
               },
     'zeigeQuote' : {     // Spaltenauswahl fuer Aufwertungsschnitt (true = anzeigen, false = nicht anzeigen)
     'zeigeJahrgang' : {   // Auswahl, ob ueber jedem Jahrgang die Ueberschriften gezeigt werden sollen oder alles in einem Block (true = Jahrgaenge, false = ein Block)
                   'Name'      : "showRatio",
                   'Name'      : "showGroupTitle",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Quote ein",
                   'Label'    : "Jahrgangs\xFCberschriften",
                   'Hotkey'    : 'T',
                   'Hotkey'    : 'J',
                   'AltLabel'  : "Quote aus",
                   'AltLabel'  : "Nur Trennlinie benutzen",
                   'AltHotkey' : 'T',
                   'AltHotkey' : 'j',
                   'FormLabel' : "Quote"
                   'FormLabel' : "Jahrg\xE4nge gruppieren"
               },
               },
     'zeigeGeb' : {        // Spaltenauswahl fuer Geburtstage (true = anzeigen, false = nicht anzeigen)
     'zeigeUxx' : {        // Auswahl, ob in der Ueberschrift ueber jedem Jahrgang zusaetzlich zur Saison noch der Jahrgang in der Form 'Uxx' angegeben wird
                   'Name'      : "showBirthday",
                   'Name'      : "showUxx",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : false,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Geburtstag ein",
                   'Label'    : "Jahrg\xE4nge anzeigen",
                   'Hotkey'    : 'G',
                   'Hotkey'    : 'U',
                   'AltLabel'  : "Geburtstag aus",
                   'AltLabel'  : "Nur Saisons anzeigen",
                   'AltHotkey' : 'G',
                   'AltHotkey' : 'u',
                   'FormLabel' : "Geburtstag"
                   'FormLabel' : "Jahrg\xE4nge U13 bis U19"
               },
               },
     'zeigeAlter' : {     // Spaltenauswahl fuer dezimales Alter (true = anzeigen, false = nicht anzeigen)
     'zeigeWarnung' : {   // Auswahl, ob eine Warnung erscheint, wenn Talente gezogen werden sollten
                   'Name'      : "showAge",
                   'Name'      : "showWarning",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Alter ein",
                   'Label'    : "Ziehwarnung ein",
                   'Hotkey'    : 'A',
                   'Hotkey'    : 'Z',
                   'AltLabel'  : "Alter aus",
                   'AltLabel'  : "Ziehwarnung aus",
                   'AltHotkey' : 'A',
                   'AltHotkey' : 'Z',
                   'FormLabel' : "Alter"
                   'FormLabel' : "Ziehwarnung"
               },
               },
     'ersetzeAlter' : {   // Spaltenauswahl fuer dezimales Alter statt ganzzahligen Alters (true = Dezimalbruch, false = ganzzahlig)
     'zeigeWarnungMonat' : { // Auswahl, ob eine Warnung erscheint, wenn zum naechsten Abrechnungs-ZAT Talente gezogen werden sollten
                   'Name'      : "substAge",
                   'Name'      : "showWarningMonth",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Alter dezimal",
                   'Label'    : "Ziehwarnung Monat ein",
                   'Hotkey'    : 'd',
                   'Hotkey'    : 'Z',
                   'AltLabel'  : "Alter ganzzahlig",
                   'AltLabel'  : "Ziehwarnung Monat aus",
                   'AltHotkey' : 'g',
                   'AltHotkey' : 'Z',
                   'FormLabel' : "Alter ersetzen"
                   'FormLabel' : "Ziehwarnung Monat"
               },
               },
     'zeigeAufw' : {       // Spaltenauswahl fuer Aufwertungen (true = anzeigen, false = nicht anzeigen)
     'zeigeWarnungHome' : { // Auswahl, ob eine Meldung im Managerbuero erscheint, wenn Talente gezogen werden sollten
                   'Name'      : "showProgresses",
                   'Name'      : "showWarningHome",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Aufwertungen ein",
                   'Label'    : "Ziehwarnung B\xFCro ein",
                   'Hotkey'    : 'W',
                   'Hotkey'    : 'z',
                   'AltLabel'  : "Aufwertungen aus",
                   'AltLabel'  : "Ziehwarnung B\xFCro aus",
                   'AltHotkey' : 'W',
                   'AltHotkey' : 'z',
                   'FormLabel' : "Aufwertungen"
                   'FormLabel' : "Ziehwarnung B\xFCro"
               },
               },
     'shortAufw' : {       // Abgekuerzte Aufwertungsanzeige
     'zeigeWarnungDialog' : { // Auswahl, ob die Meldung im Managerbuero als Dialog erscheinen soll
                   'Name'      : "shortProgresses",
                   'Name'      : "showWarningDialog",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : false,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Aufwertungen kurz",
                   'Label'    : "Ziehwarnung B\xFCro als Dialog",
                   'Hotkey'    : 'A',
                   'Hotkey'    : 'z',
                   'AltLabel'  : "Aufwertungen lang",
                   'AltLabel'  : "Ziehwarnung B\xFCro als Textmeldung",
                   'AltHotkey' : 'A',
                   'AltHotkey' : 'z',
                   'FormLabel' : "Kurze Aufwertungen"
                   'FormLabel' : "Ziehwarnung B\xFCro Dialog"
               },
               },
     'zeigeFixSkills' : {  // Spaltenauswahl fuer die Summe der Fixskills (true = anzeigen, false = nicht anzeigen)
     'zeigeWarnungAufstieg' : {  // Auswahl, ob eine Warnung in der Uebersicht erscheint, wenn Talente nach Aufstieg nicht mehr gezogen werden koennen
                   'Name'      : "showFixSkills",
                   'Name'      : "showWarningAufstieg",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Fixskills ein",
                   'Label'    : "Ziehwarnung Aufstieg ein",
                   'Hotkey'    : 'F',
                   'Hotkey'    : 'ä',
                   'AltLabel'  : "Fixskills aus",
                   'AltLabel'  : "Ziehwarnung Aufstieg aus",
                   'AltHotkey' : 'F',
                   'AltHotkey' : 'ä',
                   'FormLabel' : "Fixskills"
                   'FormLabel' : "Ziehwarnung Aufstieg"
               },
               },
     'zeigeTrainiert' : {  // Spaltenauswahl fuer die aktuellen trainierten Skills (true = anzeigen, false = nicht anzeigen)
     'zeigeWarnungLegende' : {  // Auswahl, ob eine extra Meldung in Teamuebersicht erscheint, die dort als Legende dient
                   'Name'      : "showTrainiert",
                   'Name'      : "showWarningLegende",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Trainiert ein",
                   'Label'    : "Ziehwarnung Legende ein",
                   'Hotkey'    : 'T',
                   'Hotkey'    : 'L',
                   'AltLabel'  : "Trainiert aus",
                   'AltLabel'  : "Ziehwarnung Legende aus",
                   'AltHotkey' : 'T',
                   'AltHotkey' : 'L',
                   'FormLabel' : "Trainiert"
                   'FormLabel' : "Ziehwarnung Legende"
               },
               },
     'zeigeAnteilPri' : { // Spaltenauswahl fuer den prozentualen Anteil der aktuellen Hauptskills (true = anzeigen, false = nicht anzeigen)
     'zeigeBalken' : {     // Spaltenauswahl fuer den Qualitaetsbalken des Talents (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showAnteilPri",
                   'Name'      : "showRatioBar",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Anteil Hauptskills ein",
                   'Label'    : "Balken Qualit\xE4t ein",
                   'Hotkey'    : 'H',
                   'Hotkey'    : 'B',
                   'AltLabel'  : "Anteil Hauptskills aus",
                   'AltLabel'  : "Balken Qualit\xE4t aus",
                   'AltHotkey' : 'H',
                   'AltHotkey' : 'B',
                   'FormLabel' : "Anteil Hauptskills"
                   'FormLabel' : "Balken Qualit\xE4t"
               },
               },
     'zeigeAnteilSec' : { // Spaltenauswahl fuer den prozentualen Anteil der aktuellen Nebenskills (true = anzeigen, false = nicht anzeigen)
     'absBalken' : {       // Spaltenauswahl fuer den Guetebalken des Talents absolut statt nach Foerderung (true = absolut, false = relativ nach Foerderung)
                   'Name'      : "showAnteilSec",
                   'Name'      : "absBar",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : false,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Anteil Nebenskills ein",
                   'Label'    : "Balken absolut",
                   'Hotkey'    : 'N',
                   'Hotkey'    : 'u',
                   'AltLabel'  : "Anteil Nebenskills aus",
                   'AltLabel'  : "Balken nach F\xF6rderung",
                   'AltHotkey' : 'N',
                   'AltHotkey' : 'u',
                   'FormLabel' : "Anteil Nebenskills"
                   'FormLabel' : "Balken 100%"
               },
               },
     'zeigePrios' : {     // Spaltenauswahl fuer den Schnitt der Hauptskills (true = anzeigen, false = nicht anzeigen)
     'zeigeId' : {         // Spaltenauswahl fuer Identifizierungsmerkmale der Jugendspieler (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showPrios",
                   'Name'      : "showFingerprints",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                  'Hidden'    : true,
                   'Default'  : false,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Prios ein",
                   'Label'    : "Identifikation ein",
                   'Hotkey'    : 'r',
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Prios aus",
                   'AltLabel'  : "Identifikation aus",
                   'AltHotkey' : 'r',
                   'AltHotkey' : 'T',
                   'FormLabel' : "Prios"
                   'FormLabel' : "Identifikation"
               },
               },
     'zeigeSkill' : {     // Spaltenauswahl fuer die aktuellen Werte (true = anzeigen, false = nicht anzeigen)
     'zeigeTal' : {       // Spaltenauswahl fuer Talente (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showSkill",
                   'Name'      : "showTclasses",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Skill ein",
                   'Label'    : "Talent ein",
                   'Hotkey'    : 'S',
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Skill aus",
                   'AltLabel'  : "Talent aus",
                   'AltHotkey' : 'S',
                   'AltHotkey' : 'T',
                   'FormLabel' : "Skill"
                   'FormLabel' : "Talent"
               },
               },
     'zeigePosition' : {   // Position anzeigen
     'zeigeQuote' : {     // Spaltenauswahl fuer Aufwertungsschnitt (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showPos",
                   'Name'      : "showRatio",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : false,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Position ein",
                   'Label'    : "Quote ein",
                   'Hotkey'    : 'P',
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Position aus",
                   'AltLabel'  : "Quote aus",
                   'AltHotkey' : 'P',
                   'AltHotkey' : 'T',
                   'FormLabel' : "Position"
                   'FormLabel' : "Quote"
               },
               },
     'anzahlOpti' : {     // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
     'zeigeGeb' : {       // Spaltenauswahl fuer Geburtstage (true = anzeigen, false = nicht anzeigen)
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "showBirthday",
                   'Name'      : "anzOpti",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.MC,
                   'Default'  : false,
                   'ValType: "Number",
                  'SelValue'  : false,
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                  'Default'  : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Opti: beste $",
                   'Label'    : "Geburtstag ein",
                   'Hotkey'    : 'O',
                   'Hotkey'    : 'G',
                   'FormLabel' : "Opti:|beste $"
                  'AltLabel'  : "Geburtstag aus",
                  'AltHotkey' : 'G',
                   'FormLabel' : "Geburtstag"
               },
               },
     'anzahlMW' : {       // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
     'zeigeAlter' : {      // Spaltenauswahl fuer dezimales Alter (true = anzeigen, false = nicht anzeigen)
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "showAge",
                  'Name'      : "anzMW",
                  'Type'     : __OPTTYPES.MC,
                  'ValType'  : "Number",
                  'SelValue'  : false,
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                  'Default'  : 1,
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "MW: beste $",
                  'Hotkey'    : 'M',
                  'FormLabel' : "MW:|beste $"
              },
    'zeigeTrainiertEnde' : {  // Spaltenauswahl fuer die trainierten Skills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showTrainiertEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Trainiert Ende ein",
                   'Label'    : "Alter ein",
                   'Hotkey'    : 'n',
                   'Hotkey'    : 'A',
                   'AltLabel'  : "Trainiert Ende aus",
                   'AltLabel'  : "Alter aus",
                   'AltHotkey' : 'n',
                   'AltHotkey' : 'A',
                   'FormLabel' : "Trainiert \u03A9"
                   'FormLabel' : "Alter"
               },
               },
     'zeigeAnteilPriEnde' : { // Spaltenauswahl fuer den prozentualen Anteil der Hauptskills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
     'ersetzeAlter' : {   // Spaltenauswahl fuer dezimales Alter statt ganzzahligen Alters (true = Dezimalbruch, false = ganzzahlig)
                   'Name'      : "showAnteilPriEnde",
                   'Name'      : "substAge",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : false,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Anteil Hauptskills Ende ein",
                   'Label'    : "Alter dezimal",
                   'Hotkey'    : 'u',
                   'Hotkey'    : 'd',
                   'AltLabel'  : "Anteil Hauptskills Ende aus",
                   'AltLabel'  : "Alter ganzzahlig",
                   'AltHotkey' : 'u',
                   'AltHotkey' : 'g',
                   'FormLabel' : "Anteil Hauptskills \u03A9"
                   'FormLabel' : "Alter ersetzen"
               },
               },
     'zeigeAnteilSecEnde' : { // Spaltenauswahl fuer den prozentualen Anteil der Nebenskills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
     'zeigeAufw' : {       // Spaltenauswahl fuer Aufwertungen (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showAnteilSecEnde",
                   'Name'      : "showProgresses",
                  'Type'      : __OPTTYPES.SW,
                  'Default'  : false,
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Anteil Nebenskills Ende ein",
                  'Hotkey'    : 'b',
                  'AltLabel'  : "Anteil Nebenskills Ende aus",
                  'AltHotkey' : 'b',
                  'FormLabel' : "Anteil Nebenskills \u03A9"
              },
    'zeigePriosEnde' : {  // Spaltenauswahl fuer den Schnitt der Hauptskills (true = anzeigen, false = nicht anzeigen)
                  'Name'      : "showPriosEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Prios Ende ein",
                   'Label'    : "Aufwertungen ein",
                   'Hotkey'    : 'o',
                   'Hotkey'    : 'W',
                   'AltLabel'  : "Prios Ende aus",
                   'AltLabel'  : "Aufwertungen aus",
                   'AltHotkey' : 'o',
                   'AltHotkey' : 'W',
                   'FormLabel' : "Prios \u03A9"
                   'FormLabel' : "Aufwertungen"
               },
               },
     'zeigeSkillEnde' : { // Spaltenauswahl fuer die Werte mit Ende 18 (true = anzeigen, false = nicht anzeigen)
     'shortAufw' : {       // Abgekuerzte Aufwertungsanzeige
                   'Name'      : "showSkillEnde",
                   'Name'      : "shortProgresses",
                   'Type'      : __OPTTYPES.SW,
                   'Type'      : __OPTTYPES.SW,
                   'Default'  : true,
                   'Default'  : true,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Skill Ende ein",
                   'Label'    : "Aufwertungen kurz",
                   'Hotkey'    : 'i',
                  'Hotkey'    : 'A',
                   'AltLabel'  : "Skill Ende aus",
                  'AltLabel'  : "Aufwertungen lang",
                   'AltHotkey' : 'i',
                  'AltHotkey' : 'A',
                   'FormLabel' : "Skill \u03A9"
                  'FormLabel' : "Kurze Aufwertungen"
              },
    'zeigeZatDone' : {    // Spaltenauswahl fuer die Anzahl der bisherigen Trainings-ZATs (true = anzeigen, false = nicht anzeigen)
                  'Name'      : "showFixZatDone",
                  'Type'      : __OPTTYPES.SW,
                  'Default'  : false,
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Trainings-ZATs ein",
                   'Hotkey'    : 'Z',
                   'AltLabel'  : "Trainings-ZATs aus",
                   'AltHotkey' : 'Z',
                   'FormLabel' : "Trainings-ZATs"
               },
               },
     'anzahlOptiEnde' : { // Spaltenauswahl fuer die Werte mit Ende 18:
     'zeigeZatLeft' : {   // Spaltenauswahl fuer die Anzahl der Rest-ZATs bis Ende 18 (true = anzeigen, false = nicht anzeigen)
                          // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
                   'Name'      : "showFixZatLeft",
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Type'      : __OPTTYPES.SW,
                   'Name'      : "anzOptiEnde",
                   'Default'  : false,
                   'Type'      : __OPTTYPES.MC,
                   'ValType: "Number",
                  'SelValue'  : false,
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                  'Default'  : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Opti Ende: beste $",
                   'Label'    : "Rest-ZATs ein",
                   'Hotkey'    : 't',
                   'Hotkey'    : 'R',
                   'FormLabel' : "Opti \u03A9:|beste $"
                  'AltLabel'  : "Rest-ZATs aus",
                  'AltHotkey' : 'R',
                   'FormLabel' : "Rest-ZATs"
               },
               },
     'anzahlMWEnde' : {   // Spaltenauswahl fuer die Werte mit Ende 18:
     'zeigeFixSkills' : { // Spaltenauswahl fuer die Summe der Fixskills (true = anzeigen, false = nicht anzeigen)
                          // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
                   'Name'      : "showFixSkills",
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Type'      : __OPTTYPES.SW,
                   'Name'      : "anzMWEnde",
                   'Default'  : true,
                   'Type'      : __OPTTYPES.MC,
                  'ValType'  : "Number",
                  'SelValue'  : false,
                  'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'  : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "MW Ende: beste $",
                   'Label'    : "Fixskills ein",
                   'Hotkey'    : 'W',
                   'Hotkey'    : 'F',
                   'FormLabel' : "MW \u03A9:|beste $"
                  'AltLabel'  : "Fixskills aus",
                  'AltHotkey' : 'F',
                   'FormLabel' : "Fixskills"
               },
               },
     'kennzeichenEnde' : {  // Markierung fuer Ende 18
     'zeigeTrainiert' : {  // Spaltenauswahl fuer die aktuellen trainierten Skills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "charEnde",
                   'Name'      : "showTrainiert",
                   'Type'      : __OPTTYPES.MC,
                   'Type'      : __OPTTYPES.SW,
                   'ValType: "String",
                   'Default'  : true,
                  'FreeValue' : true,
                  'MinChoice' : 0,
                  'Choice'    : [ " \u03A9", " 18" ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Ende: $",
                   'Label'    : "Trainiert ein",
                   'Hotkey'    : 'E',
                   'Hotkey'    : 'T',
                   'FormLabel' : "Ende 18:|$"
                  'AltLabel'  : "Trainiert aus",
                  'AltHotkey' : 'T',
                   'FormLabel' : "Trainiert"
               },
               },
     'sepStyle' : {       // Stil der Trennlinie
     'zeigeAnteilPri' : { // Spaltenauswahl fuer den prozentualen Anteil der aktuellen Hauptskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "sepStyle",
                   'Name'      : "showAnteilPri",
                   'Type'      : __OPTTYPES.MC,
                   'Type'      : __OPTTYPES.SW,
                   'ValType'  : "String",
                   'Default'  : true,
                  'Choice'    : [ "solid", "hidden", "dotted", "dashed", "double", "groove", "ridge",
                                  "inset", "outset", "none" ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Stil: $",
                   'Label'    : "Anteil Hauptskills ein",
                   'Hotkey'    : 'l',
                   'Hotkey'    : 'H',
                   'FormLabel' : "Stil:|$"
                  'AltLabel'  : "Anteil Hauptskills aus",
                  'AltHotkey' : 'H',
                   'FormLabel' : "Anteil Hauptskills"
               },
               },
     'sepColor' : {       // Farbe der Trennlinie
     'zeigeAnteilSec' : { // Spaltenauswahl fuer den prozentualen Anteil der aktuellen Nebenskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "sepColor",
                   'Name'      : "showAnteilSec",
                   'Type'      : __OPTTYPES.MC,
                   'Type'      : __OPTTYPES.SW,
                   'ValType'  : "String",
                   'Default'  : false,
                  'FreeValue' : true,
                  'Choice'    : [ "white", "yellow", "black", "blue", "cyan", "gold", "grey", "green",
                                  "lime", "magenta", "maroon", "navy", "olive", "orange", "purple",
                                  "red", "teal", "transparent" ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Farbe: $",
                   'Label'    : "Anteil Nebenskills ein",
                   'Hotkey'    : 'F',
                   'Hotkey'    : 'N',
                   'FormLabel' : "Farbe:|$"
                  'AltLabel'  : "Anteil Nebenskills aus",
                  'AltHotkey' : 'N',
                   'FormLabel' : "Anteil Nebenskills"
               },
               },
     'sepWidth' : {       // Dicke der Trennlinie
     'zeigePrios' : {     // Spaltenauswahl fuer den Schnitt der Hauptskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "sepWidth",
                   'Name'      : "showPrios",
                   'Type'      : __OPTTYPES.MC,
                   'Type'      : __OPTTYPES.SW,
                   'ValType: "String",
                   'Default'  : true,
                  'FreeValue' : true,
                  'Choice'    : [ "thin", "medium", "thick" ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Dicke: $",
                   'Label'    : "Prios ein",
                   'Hotkey'    : 'D',
                   'Hotkey'    : 'r',
                   'FormLabel' : "Dicke:|$"
                  'AltLabel'  : "Prios aus",
                  'AltHotkey' : 'r',
                   'FormLabel' : "Prios"
               },
               },
     'saison' : {         // Laufende Saison
     'zeigeSkill' : {     // Spaltenauswahl fuer die aktuellen Werte (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "saison",
                  'Name'      : "showSkill",
                  'Type'      : __OPTTYPES.SW,
                  'Default'  : true,
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Skill ein",
                  'Hotkey'    : 'S',
                  'AltLabel'  : "Skill aus",
                  'AltHotkey' : 'S',
                  'FormLabel' : "Skill"
              },
    'zeigePosition' : {  // Position anzeigen
                  'Name'      : "showPos",
                  'Type'      : __OPTTYPES.SW,
                  'Default'  : false,
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Position ein",
                  'Hotkey'    : 'P',
                  'AltLabel'  : "Position aus",
                  'AltHotkey' : 'P',
                  'FormLabel' : "Position"
              },
    'anzahlOpti' : {      // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "anzOpti",
                   'Type'      : __OPTTYPES.MC,
                   'Type'      : __OPTTYPES.MC,
                   'ValType'  : "Number",
                   'ValType'  : 'Number',
                  'FreeValue' : true,
                   'SelValue'  : false,
                   'SelValue'  : false,
                   'Choice'    : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ],
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'  : 10,
                   'Default'  : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Saison: $",
                   'Label'    : "Opti: beste $",
                   'Hotkey'    : 'a',
                   'Hotkey'    : 'O',
                   'FormLabel' : "Saison:|$"
                   'FormLabel' : "Opti:|beste $"
               },
               },
     'aktuellerZat' : {   // Laufender ZAT
     'anzahlMW' : {       // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
                   'Name'      : "currZAT",
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "anzMW",
                   'Type'      : __OPTTYPES.MC,
                   'Type'      : __OPTTYPES.MC,
                   'ValType'  : "Number",
                   'ValType'  : 'Number',
                  'Permanent' : true,
                   'SelValue'  : false,
                   'SelValue'  : false,
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6,  7,  8,  9, 10, 11,
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                                  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
                  'Default'  : 1,
                                  24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
                                  36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
                                  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
                                  60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71 ],
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "ZAT: $",
                   'Label'    : "MW: beste $",
                   'Hotkey'    : 'Z',
                   'Hotkey'    : 'M',
                   'FormLabel' : "ZAT:|$"
                   'FormLabel' : "MW:|beste $"
               },
               },
     'datenZat' : {       // Stand der Daten zum Team und ZAT
     'zeigeTrainiertEnde' : { // Spaltenauswahl fuer die trainierten Skills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "dataZAT",
                   'Name'      : "showTrainiertEnde",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.SW,
                   'ValType: "Number",
                   'Default'  : true,
                  'Hidden'    : true,
                   'Action'    : __OPTACTION.NXT,
                   'Serial'    : true,
                   'Label'     : "Trainiert Ende ein",
                   'AutoReset' : true,
                   'Hotkey'    : 'n',
                  'Permanent' : true,
                   'AltLabel' : "Trainiert Ende aus",
                   'Default'  : undefined,
                   'AltHotkey' : 'n',
                  'Action'    : __OPTACTION.SET,
                   'FormLabel' : "Trainiert \u03A9"
                  'Submit'   : undefined,
                   'Cols'     : 1,
                   'Rows'     : 1,
                  'Replace'   : null,
                   'Space'     : 0,
                  'Label'    : "Daten-ZAT:"
               },
               },
     'team' : {           // Datenspeicher fuer Daten des Erst- bzw. Zweitteams
     'zeigeAnteilPriEnde' : { // Spaltenauswahl fuer den prozentualen Anteil der Hauptskills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "team",
                   'Name'      : "showAnteilPriEnde",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.SW,
                   'Hidden'   : false,
                   'Default'   : false,
                   'Serial'    : true,
                   'Action'    : __OPTACTION.NXT,
                   'Permanent' : true,
                   'Label'     : "Anteil Hauptskills Ende ein",
                  'Default'  : undefined,  // new Team() // { 'Team' : undefined, 'Liga' : undefined, 'Land' : undefined, 'LdNr' : 0, 'LgNr' : 0 }
                   'Hotkey'    : 'u',
                   'Submit'    : undefined,
                   'AltLabel' : "Anteil Hauptskills Ende aus",
                  'Cols'     : 36,
                   'AltHotkey' : 'u',
                   'Rows'     : 6,
                   'FormLabel' : "Anteil Hauptskills \u03A9"
                   'Replace'   : null,
                  'Space'     : 1,
                   'Label'     : "Verein:"
               },
               },
     'birthdays' : {       // Datenspeicher fuer Geburtstage der Jugendspieler
     'zeigeAnteilSecEnde' : { // Spaltenauswahl fuer den prozentualen Anteil der Nebenskills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "birthdays",
                   'Name'      : "showAnteilSecEnde",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.SW,
                   'Hidden'   : true,
                   'Default'   : false,
                   'Serial'    : true,
                   'Action'    : __OPTACTION.NXT,
                   'AutoReset' : true,
                   'Label'     : "Anteil Nebenskills Ende ein",
                   'Permanent' : true,
                   'Hotkey'    : 'b',
                  'Default'  : [],
                   'AltLabel' : "Anteil Nebenskills Ende aus",
                  'Submit'    : undefined,
                   'AltHotkey' : 'b',
                  'Cols'     : 36,
                   'FormLabel' : "Anteil Nebenskills \u03A9"
                   'Rows'     : 2,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Geburtstage:"
               },
               },
     'tClasses' : {       // Datenspeicher fuer Talente der Jugendspieler (-1=wenig, 0=normal, +1=hoch)
     'zeigePriosEnde' : { // Spaltenauswahl fuer den Schnitt der Hauptskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "tClasses",
                   'Name'      : "showPriosEnde",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.SW,
                   'Hidden'   : true,
                   'Default'   : true,
                   'Serial'    : true,
                   'Action'    : __OPTACTION.NXT,
                   'AutoReset' : true,
                   'Label'     : "Prios Ende ein",
                   'Permanent' : true,
                   'Hotkey'    : 'o',
                  'Default'  : [],
                   'AltLabel' : "Prios Ende aus",
                  'Submit'    : undefined,
                   'AltHotkey' : 'o',
                  'Cols'     : 36,
                   'FormLabel' : "Prios \u03A9"
                   'Rows'     : 2,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Talente:"
               },
               },
     'progresses' : {     // Datenspeicher fuer Aufwertungen der Jugendspieler (als Strings)
     'zeigeSkillEnde' : { // Spaltenauswahl fuer die Werte mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "progresses",
                   'Name'      : "showSkillEnde",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.SW,
                   'Hidden'   : true,
                   'Default'   : true,
                   'Serial'    : true,
                   'Action'    : __OPTACTION.NXT,
                   'AutoReset' : true,
                   'Label'     : "Skill Ende ein",
                   'Permanent' : true,
                   'Hotkey'    : 'i',
                  'Default'  : [],
                   'AltLabel' : "Skill Ende aus",
                  'Submit'    : undefined,
                   'AltHotkey' : 'i',
                  'Cols'     : 36,
                   'FormLabel' : "Skill \u03A9"
                   'Rows'     : 7,
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Aufwertungen:"
               },
               },
     'zatAges' : {         // Datenspeicher fuer (gebrochene) Alter der Jugendspieler
     'anzahlOptiEnde' : { // Spaltenauswahl fuer die Werte mit Ende 18:
                   'Name'      : "zatAges",
                          // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
                   'Type'      : __OPTTYPES.SD,
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Hidden'   : true,
                   'Name'      : "anzOptiEnde",
                  'Serial'   : true,
                   'Type'      : __OPTTYPES.MC,
                   'AutoReset' : true,
                   'ValType'   : 'Number',
                   'Permanent' : true,
                   'SelValue' : false,
                   'Default'  : [],
                   'Choice'   : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Submit'    : undefined,
                   'Default'  : 1,
                   'Cols'     : 36,
                   'Action'    : __OPTACTION.NXT,
                  'Rows'      : 2,
                   'Label'     : "Opti Ende: beste $",
                   'Replace'   : null,
                   'Hotkey'   : 't',
                  'Space'     : 0,
                   'FormLabel' : "Opti \u03A9:|beste $"
                   'Label'     : "ZAT-Alter:"
               },
               },
     'trainiert' : {       // Datenspeicher fuer Trainingsquoten der Jugendspieler
     'anzahlMWEnde' : {   // Spaltenauswahl fuer die Werte mit Ende 18:
                   'Name'      : "numProgresses",
                          // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
                   'Type'      : __OPTTYPES.SD,
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Hidden'   : true,
                   'Name'      : "anzMWEnde",
                  'Serial'   : true,
                   'Type'      : __OPTTYPES.MC,
                   'AutoReset' : true,
                   'ValType'   : 'Number',
                   'Permanent' : true,
                   'SelValue' : false,
                   'Default'  : [],
                   'Choice'   : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Submit'    : undefined,
                   'Default'  : 1,
                   'Cols'     : 36,
                   'Action'    : __OPTACTION.NXT,
                  'Rows'      : 2,
                   'Label'     : "MW Ende: beste $",
                   'Replace'   : null,
                   'Hotkey'   : 'W',
                  'Space'     : 0,
                   'FormLabel' : "MW \u03A9:|beste $"
                   'Label'     : "Trainiert:"
               },
               },
     'positions' : {       // Datenspeicher fuer optimale Positionen der Jugendspieler
     'kennzeichenEnde' : { // Markierung fuer Ende 18
                   'Name'      : "positions",
                   'Name'      : "charEnde",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.MC,
                   'Hidden'   : true,
                   'ValType'   : 'String',
                  'Serial'   : true,
                   'FreeValue' : true,
                   'AutoReset' : true,
                   'MinChoice' : 0,
                   'Permanent' : true,
                   'Choice'   : [ " \u03A9", " 18" ],
                   'Default'   : [],
                   'Action'    : __OPTACTION.NXT,
                   'Submit'    : undefined,
                   'Label'     : "Ende: $",
                   'Cols'     : 36,
                   'Hotkey'   : 'E',
                  'Rows'      : 3,
                   'FormLabel' : "Ende 18:|$"
                   'Replace'   : null,
                  'Space'     : 0,
                   'Label'     : "Positionen:"
               },
               },
     'skills' : {         // Datenspeicher fuer aktuelle Einzelskills der Jugendspieler
     'sepStyle' : {       // Stil der Trennlinie
                   'Name'      : "skills",
                   'Name'      : "sepStyle",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.MC,
                   'Hidden'    : true,
                   'ValType'  : 'String',
                   'Serial'    : true,
                  'Choice'    : [ 'solid', 'hidden', 'dotted', 'dashed', 'double', 'groove', 'ridge',
                   'AutoReset' : true,
                                  'inset', 'outset', 'none' ],
                   'Permanent' : true,
                  'Action'    : __OPTACTION.NXT,
                   'Default'   : [],
                  'Label'    : "Stil: $",
                   'Submit'    : undefined,
                   'Hotkey'    : 'l',
                   'Cols'      : 36,
                  'FormLabel' : "Stil:|$"
                   'Rows'      : 20,
              },
                   'Replace'  : null,
    'sepColor' : {        // Farbe der Trennlinie
                   'Space'     : 0,
                  'Name'      : "sepColor",
                   'Label'    : "Skills:"
                  'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'String',
                   'FreeValue' : true,
                   'Choice'   : [ 'white', 'yellow', 'black', 'blue', 'cyan', 'gold', 'grey', 'green',
                                  'lime', 'magenta', 'maroon', 'navy', 'olive', 'orange', 'purple',
                                  'red', 'teal', 'transparent' ],
                   'Action'    : __OPTACTION.NXT,
                  'Label'    : "Farbe: $",
                  'Hotkey'    : 'F',
                  'FormLabel' : "Farbe:|$"
              },
    'sepWidth' : {        // Dicke der Trennlinie
                   'Name'      : "sepWidth",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'  : 'String',
                  'FreeValue' : true,
                  'Choice'    : [ 'thin', 'medium', 'thick' ],
                   'Action'   : __OPTACTION.NXT,
                   'Label'    : "Dicke: $",
                  'Hotkey'    : 'D',
                  'FormLabel' : "Dicke:|$"
               },
               },
     'hauptLS' : {       // Option 'ligaSize' aus Modul 'OS2.haupt', hier als 'hauptLS'
     'saison' : {         // Laufende Saison
                   'Shared'    : { /*'namespace' : "http://os.ongapo.com/",*/ 'module' : "OS2.haupt", 'item' : 'ligaSize' },
                  'Name'     : "saison",
                   'Hidden'    : true,
                  'Type'     : __OPTTYPES.MC,
                   'FormLabel' : "Liga:|$er (haupt)"
                  'ValType'  : 'Number',
              },
                  'FreeValue' : true,
     'hauptZat' : {       // Option 'datenZat' aus Modul 'OS2.haupt', hier als 'hauptZat'
                   'SelValue'  : false,
                   'Shared'    : { /*'namespace' : "http://os.ongapo.com/",*/ 'module' : "OS2.haupt", 'item' : 'datenZat' },
                  'Choice'    : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],
                   'Hidden'   : true,
                  'Default'   : 16,
                   'Cols'     : 36,
                  'Action'   : __OPTACTION.NXT,
                   'Rows'     : 6,
                  'Label'     : "Saison: $",
                   'Label'    : "ZAT:"
                   'Hotkey'    : 'a',
              },
                   'FormLabel' : "Saison:|$"
    'haupt' : {          // Alle Optionen des Moduls 'OS2.haupt'
              },
                   'Shared'   : { 'module' : "OS2.haupt", 'item' : '$' },
     'aktuellerZat' : {   // Laufender ZAT
                   'Name'     : "currZAT",
                  'Type'     : __OPTTYPES.MC,
                  'ValType'   : 'Number',
                   'Permanent' : true,
                   'SelValue'  : false,
                  'Choice'   : [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
                                  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
                                  24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
                                  36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
                                  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
                                  60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
                                  72 ],
                   'Action'   : __OPTACTION.NXT,
                   'Label'    : "ZAT: $",
                  'Hotkey'   : 'Z',
                   'FormLabel' : "ZAT:|$"
              },
    'datenZat' : {        // Stand der Daten zum Team und ZAT
                  'Name'     : "dataZAT",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.SD,
                  'ValType'  : 'Number',
                   'Hidden'    : true,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'Serial'    : true,
                   'Cols'      : 36,
                  'AutoReset' : true,
                   'Rows'      : 6,
                  'Permanent' : true,
                  'Default'  : undefined,
                  'Action'    : __OPTACTION.SET,
                  'Submit'    : undefined,
                   'Cols'      : 1,
                   'Rows'      : 1,
                   'Replace'  : null,
                   'Replace'  : null,
                   'Space'    : 4,
                   'Space'    : 0,
                   'Label'    : "Haupt:"
                   'Label'    : "Daten-ZAT:"
              },
              },
     'data' : {           // Optionen aller Module
     'oldDatenZat' : {     // Stand der Daten zum Team und ZAT
                   'Shared'   : { 'module' : '$' },
                   'Name'     : "oldDataZAT",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.SD,
                  'ValType'  : 'Number',
                   'Hidden'    : true,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'Serial'    : true,
                   'Cols'      : 36,
                  'AutoReset' : true,
                   'Rows'      : 6,
                  'Permanent' : true,
                  'Default'  : undefined,
                  'Action'    : __OPTACTION.SET,
                  'Submit'    : undefined,
                   'Cols'      : 1,
                   'Rows'      : 1,
                   'Replace'  : null,
                   'Replace'  : null,
                   'Space'    : 4,
                   'Space'    : 0,
                   'Label'    : "Data:"
                   'Label'    : "Vorheriger Daten-ZAT:"
              },
    'reset' : {          // Optionen auf die "Werkseinstellungen" zuruecksetzen
                  'Name'      : "reset",
                  'Type'      : __OPTTYPES.SI,
                  'Action'    : __OPTACTION.RST,
                  'Label'    : "Standard-Optionen",
                  'Hotkey'    : 'r',
                  'FormLabel' : ""
               },
               },
     'storage' : {         // Browserspeicher fuer die Klicks auf Optionen
     'foerderung' : {     // Jugendfoerderung
                   'Name'      : "storage",
                   'Name'      : "donation",
                   'Type'      : __OPTTYPES.MC,
                   'Type'      : __OPTTYPES.MC,
                   'ValType'  : "String",
                   'ValType'  : 'Number',
                   'Choice'    : Object.keys(__OPTMEM),
                  'Permanent' : true,
                  'SelValue'  : false,
                   'Choice'    : [ 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000,
                                  5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000 ],
                  'Default'  : 10000,
                   'Action'    : __OPTACTION.NXT,
                   'Action'    : __OPTACTION.NXT,
                   'Label'    : "Speicher: $",
                   'Label'    : "F\xF6rderung: $",
                   'Hotkey'    : 'c',
                   'Hotkey'    : 'F',
                   'FormLabel' : "Speicher:|$"
                   'FormLabel' : "F\xF6rderung:|$"
               },
               },
     'oldStorage' : {     // Vorheriger Browserspeicher fuer die Klicks auf Optionen
     'team' : {           // Datenspeicher fuer Daten des Erst- bzw. Zweitteams
                   'Name'      : "oldStorage",
                   'Name'      : "team",
                   'Type'      : __OPTTYPES.SD,
                   'Type'      : __OPTTYPES.SD,
                  'PreInit'  : true,
                   'Hidden'    : false,
                  'AutoReset' : true,
                   'Serial'   : true,
                   'Hidden'    : true
              },
    'showForm' : {        // Optionen auf der Webseite (true = anzeigen, false = nicht anzeigen)
                  'Name'      : "showForm",
                   'Type'     : __OPTTYPES.SW,
                  'FormType'  : __OPTTYPES.SI,
                   'Permanent' : true,
                   'Permanent' : true,
                   'Default'  : false,
                   'Default'  : undefined, // new Team() // { 'Team' : undefined, 'Liga' : undefined, 'Land' : undefined, 'LdNr' : 0, 'LgNr' : 0 }
                  'Action'   : __OPTACTION.NXT,
                   'Submit'   : undefined,
                  'Label'     : "Optionen anzeigen",
                   'Cols'     : 36,
                  'Hotkey'   : 'O',
                   'Rows'     : 6,
                   'AltLabel' : "Optionen verbergen",
                  'Replace'  : null,
                   'AltHotkey' : 'O',
                  'Space'    : 1,
                   'FormLabel' : ""
                  'Label'    : "Verein:"
               }
               },
};
    'fingerprints' : {    // Datenspeicher fuer Identifizierungsmerkmale der Jugendspieler
 
                  'Name'      : "fingerprints",
// ==================== Invarianter Abschnitt fuer Optionen ====================
                  'Type'      : __OPTTYPES.SD,
 
                  'Hidden'    : true,
// Kompatibilitaetsfunktion zur Ermittlung des Namens einer Funktion (falle <Function>.name nicht vorhanden ist)
                  'Serial'    : true,
if (Function.prototype.name === undefined) {
                  'AutoReset' : true,
    Object.defineProperty(Function.prototype, 'name', {
                  'Permanent' : true,
            get : function() {
                  'Default'  : [],
                      return /function ([^(\s]*)/.exec(this.toString())[1];
                  'Submit'    : undefined,
                  }
                  'Cols'      : 36,
        });
                  'Rows'      : 6,
}
                  'Replace'  : null,
 
                  'Space'    : 0,
// Ein Satz von Logfunktionen, die je nach Loglevel zur Verfuegung stehen. Aufruf: __LOG[level](text)
                  'Label'    : "Identifikation:"
const __LOG = {
              },
                  'logFun'  : [
    'birthdays' : {      // Datenspeicher fuer Geburtstage der Jugendspieler
                                  console.error,  // [0] Alert
                  'Name'      : "birthdays",
                                  console.error, // [1] Error
                  'Type'      : __OPTTYPES.SD,
                                  console.log,    // [2] Log: Release
                  'Hidden'   : true,
                                  console.log,   // [3] Log: Info
                  'Serial'    : true,
                                  console.log,    // [4] Log: Debug
                  'AutoReset' : true,
                                  console.log,    // [5] Log: Verbose
                  'Permanent' : true,
                                  console.log    // [6] Log: Very verbose
                  'Default'  : [],
                              ],
                  'Submit'    : undefined,
                  'init'     : function(win, logLevel = 1) {
                  'Cols'      : 36,
                                  for (level = 0; level < this.logFun.length; level++) {
                  'Rows'     : 2,
                                      this[level] = ((level > logLevel) ? function() { } : this.logFun[level]);
                  'Replace'  : null,
                                  }
                  'Space'    : 0,
                              },
                  'Label'    : "Geburtstage:"
                  'changed' : function(oldVal, newVal) {
              },
                                  const __OLDVAL = safeStringify(oldVal);
    'tClasses' : {        // Datenspeicher fuer Talente der Jugendspieler (-1=wenig, 0=normal, +1=hoch)
                                  const __NEWVAL = safeStringify(newVal);
                  'Name'      : "tClasses",
 
                  'Type'      : __OPTTYPES.SD,
                                  return ((__OLDVAL !== __NEWVAL) ? __OLDVAL + " => " : "") + __NEWVAL;
                  'Hidden'    : true,
                              }
                  'Serial'    : true,
              };
                  'AutoReset' : true,
 
                  'Permanent' : true,
__LOG.init(window, __LOGLEVEL);
                  'Default'  : [],
 
                  'Submit'    : undefined,
// Gibt eine Meldung in der Console aus und oeffnet ein Bestaetigungsfenster mit der Meldung
                  'Cols'      : 36,
// label: Eine Ueberschrift
                  'Rows'      : 2,
// message: Der Meldungs-Text
                  'Replace'  : null,
// data: Ein Wert. Ist er angegeben, wird er in der Console ausgegeben
                  'Space'     : 0,
function showAlert(label, message, data = undefined) {
                  'Label'     : "Talente:"
    __LOG[1](label + ": " + message);
              },
 
     'progresses' : {      // Datenspeicher fuer Aufwertungen der Jugendspieler (als Strings)
     if (data !== undefined) {
                  'Name'      : "progresses",
        __LOG[2](data);
                  'Type'      : __OPTTYPES.SD,
     }
                  'Hidden'    : true,
 
                  'Serial'    : true,
     alert(label + "\n\n" + message);
                  'AutoReset' : true,
}
                  'Permanent' : true,
 
                  'Default'  : [],
// ==================== Abschnitt fuer Klasse Class ====================
                  'Submit'   : undefined,
 
                  'Cols'      : 36,
function Class(className, baseClass, initFun) {
                  'Rows'      : 7,
    'use strict';
                  'Replace'  : null,
 
                  'Space'    : 0,
    try {
                  'Label'    : "Aufwertungen:"
        const __BASE = ((baseClass !== undefined) ? baseClass : Object);
              },
        const __BASEPROTO = (__BASE ? __BASE.prototype : undefined);
    'ziehAnz' : {         // Datenspeicher fuer Anzahl zu ziehender Jugendspieler bis zur naechsten Abrechnung
        const __BASECLASS = (__BASEPROTO ? __BASEPROTO.__class : undefined);
                  'Name'     : "drawCounts",
 
                  'Type'      : __OPTTYPES.SD,
        this.className = (className || '?');
                  'Hidden'    : true,
         this.baseClass = __BASECLASS;
                  'Serial'    : true,
        Object.setConst(this, 'baseProto', __BASEPROTO, false);
                  'AutoReset' : false,
 
                  'Permanent' : true,
        if (! initFun) {
                  'Default'  : [],
            const __BASEINIT = (__BASECLASS || { }).init;
                  'Submit'    : undefined,
 
                  'Cols'      : 25,
            if (__BASEINIT) {
                  'Rows'      : 1,
                initFun = function() {
                  'Replace'  : null,
                              // Basisklassen-Init aufrufen...
                  'Space'    : 0,
                              return __BASEINIT.call(this, arguments);
                  'Label'     : "Zu ziehen:"
                          };
              },
            } else {
    'ziehAnzAufstieg' : { // Datenspeicher fuer Anzahl zu ziehender Jugendspieler bis zur naechsten Abrechnung im Falle eines Aufstiegs
                initFun = function() {
                  'Name'     : "drawCountsAufstieg",
                              // Basisklassen-Init fehlt (und Basisklasse ist nicht Object)...
                  'Type'      : __OPTTYPES.MC,
                              return false;
                  'ValType'  : 'Number',
                          };
                  'Hidden'    : true,
            }
                  'AutoReset' : false,
        }
                  'Permanent' : true,
 
                  'FreeValue' : true,
        console.assert((__BASE === null) || ((typeof __BASE) === 'function'), "No function:", __BASE);
                  'SelValue'  : false,
        console.assert((typeof initFun) === 'function', "No function:", initFun);
                  'Choice'    : [ 0, 1, 2, 3, 4, 5 ],
 
                  'Default'  : 0,
        this.init = initFun;
                  'Action'    : __OPTACTION.NXT,
    } catch (ex) {
                  'Label'    : "Zu ziehen bei Aufstieg: $",
        showAlert('[' + ex.lineNumber + "] Error in Class " + className, ex.message, ex);
                  'Hotkey'    : 'z',
    }
                  'FormLabel' : "Zu ziehen bei Aufstieg:|$"
}
              },
 
     'zatAges' : {        // Datenspeicher fuer (gebrochene) Alter der Jugendspieler
Class.define = function(subClass, baseClass, members = undefined, initFun = undefined, createProto = true) {
                  'Name'      : "zatAges",
        return (subClass.prototype = subClass.subclass(baseClass, members, initFun, createProto));
                  'Type'      : __OPTTYPES.SD,
    };
                  'Hidden'   : true,
 
                  'Serial'   : true,
Object.setConst = function(obj, item, value, config) {
                  'AutoReset' : true,
        return Object.defineProperty(obj, item, {
                  'Permanent' : true,
                        enumerable  : false,
                  'Default'  : [],
                        configurable : (config || true),
                  'Submit'    : undefined,
                        writable     : false,
                  'Cols'      : 36,
                        value        : value
                  'Rows'      : 2,
                    });
                  'Replace'   : null,
    };
                  'Space'     : 0,
 
                  'Label'    : "ZAT-Alter:"
Object.setConst(Object.prototype, 'subclass', function(baseClass, members, initFun, createProto) {
              },
        'use strict';
    'trainiert' : {       // Datenspeicher fuer Trainingsquoten der Jugendspieler
 
                  'Name'     : "numProgresses",
        try {
                  'Type'     : __OPTTYPES.SD,
            const __MEMBERS = (members || { });
                  'Hidden'    : true,
            const __CREATEPROTO = ((createProto === undefined) ? true : createProto);
                  'Serial'   : true,
 
                  'AutoReset' : true,
            console.assert((typeof this) === 'function');
                  'Permanent' : true,
            console.assert((typeof __MEMBERS) === 'object');
                  'Default'   : [],
 
                  'Submit'    : undefined,
            const __CLASS = new Class(this.name || __MEMBERS.__name, baseClass, initFun || __MEMBERS.__init);
                  'Cols'      : 36,
            const __PROTO = (__CREATEPROTO ? Object.create(__CLASS.baseProto) : this.prototype);
                  'Rows'      : 2,
 
                  'Replace'  : null,
            for (let item in __MEMBERS) {
                  'Space'     : 0,
                if ((item !== '__name') && (item !== '__init')) {
                  'Label'    : "Trainiert:"
                    Object.setConst(__PROTO, item, __MEMBERS[item]);
              },
                }
    'positions' : {       // Datenspeicher fuer optimale Positionen der Jugendspieler
            }
                  'Name'      : "positions",
 
                  'Type'     : __OPTTYPES.SD,
            Object.setConst(__PROTO, '__class', __CLASS, ! __CREATEPROTO);
                  'Hidden'    : true,
 
                  'Serial'    : true,
            return __PROTO;
                  'AutoReset' : true,
        } catch (ex) {
                  'Permanent' : true,
            showAlert('[' + ex.lineNumber + "] Error in subclassing", ex.message, ex);
                  'Default'  : [],
        }
                  'Submit'    : undefined,
    }, false);
                  'Cols'      : 36,
 
                  'Rows'      : 3,
Class.define(Object, null, {
                  'Replace'  : null,
                    '__init'       : function() {
                  'Space'    : 0,
                                        // Oberstes Basisklassen-Init...
                  'Label'    : "Positionen:"
                                        return true;
              },
                                    },
    'skills' : {          // Datenspeicher fuer aktuelle Einzelskills der Jugendspieler
                    'getClass'    : function() {
                  'Name'      : "skills",
                                        return this.__class;
                  'Type'      : __OPTTYPES.SD,
                                    },
                  'Hidden'    : true,
                    'getClassName' : function() {
                  'Serial'    : true,
                                        const __CLASS = this.getClass();
                  'AutoReset' : true,
 
                  'Permanent' : true,
                                        return (__CLASS ? __CLASS.getName() : undefined);
                  'Default'  : [],
                                    },
                  'Submit'    : undefined,
                    'setConst'     : function(item, value, config = undefined) {
                  'Cols'      : 36,
                                        return Object.setConst(this, item, value, config);
                  'Rows'      : 20,
                                    }
                  'Replace'  : null,
                }, undefined, false);
                  'Space'     : 0,
 
                  'Label'     : "Skills:"
Class.define(Function, Object);
              },
 
    'hauptLS'  : {       // Option 'ligaSize' aus Modul 'OS2.haupt', hier als 'hauptLS'
Class.define(Class, Object, {
                  'Shared'    : { /*'namespace' : "http://os.ongapo.com/",*/ 'module' : "OS2.haupt", 'item' : 'ligaSize' },
                    'getName'      : function() {
                  'Hidden'    : true,
                                        return this.className;
                  'FormLabel' : "Liga:|$er (haupt)"
                                    }
              },
                } );
    'hauptZat' : {        // Option 'datenZat' aus Modul 'OS2.haupt', hier als 'hauptZat'
 
                  'Shared'    : { /*'namespace' : "http://os.ongapo.com/",*/ 'module' : "OS2.haupt", 'item' : 'datenZat' },
// ==================== Ende Abschnitt fuer Klasse Class ====================
                  'Hidden'    : true,
 
                  'Cols'      : 36,
// ==================== Abschnitt fuer Klasse Delims ====================
                  'Rows'      : 6,
 
                  'Label'    : "ZAT:"
// Basisklasse fuer die Verwaltung der Trennzeichen und Symbole von Pfaden
              },
// delim: Trennzeichen zwischen zwei Ebenen (oder Objekt/Delims mit entsprechenden Properties)
    'haupt' : {          // Alle Optionen des Moduls 'OS2.haupt'
// back: (Optional) Name des relativen Vaterverzeichnisses
                  'Shared'    : { 'module' : "OS2.haupt", 'item' : '$' },
// root: (Optional) Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
                  'Type'      : __OPTTYPES.SD,
// home: (Optional) Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
                  'Hidden'    : true,
function Delims(delim, back, root, home) {
                  'Serial'    : true,
    'use strict';
                  'Cols'      : 36,
 
                  'Rows'      : 6,
    if ((typeof delim) === 'object') {
                  'Replace'  : null,
        // Erster Parameter ist Objekt mit den Properties...
                  'Space'    : 4,
        if (back === undefined) {
                  'Label'    : "Haupt:"
            back = delim.back;
              },
        }
    'data' : {            // Optionen aller Module
        if (root === undefined) {
                  'Shared'    : { 'module' : '$' },
            root = delim.root;
                  'Type'      : __OPTTYPES.SD,
        }
                  'Hidden'    : true,
        if (home === undefined) {
                  'Serial'    : true,
            home = delim.home;
                  'Cols'      : 36,
        }
                  'Rows'      : 6,
        delim = delim.delim;
                  'Replace'  : null,
    }
                  'Space'    : 4,
                  'Label'    : "Data:"
              },
    'reset' : {          // Optionen auf die "Werkseinstellungen" zuruecksetzen
                  'FormPrio'  : undefined,
                  'Name'      : "reset",
                  'Type'      : __OPTTYPES.SI,
                  'Action'    : __OPTACTION.RST,
                  'Label'    : "Standard-Optionen",
                  'Hotkey'    : 'r',
                  'FormLabel' : ""
              },
    'storage' : {        // Browserspeicher fuer die Klicks auf Optionen
                  'FormPrio'  : undefined,
                  'Name'      : "storage",
                  'Type'      : __OPTTYPES.MC,
                  'ValType'  : 'String',
                  'Choice'    : Object.keys(__OPTMEM),
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Speicher: $",
                  'Hotkey'    : 'c',
                  'FormLabel' : "Speicher:|$"
              },
    'oldStorage' : {      // Vorheriger Browserspeicher fuer die Klicks auf Optionen
                  'FormPrio'  : undefined,
                  'Name'      : "oldStorage",
                  'Type'      : __OPTTYPES.SD,
                  'PreInit'  : true,
                  'AutoReset' : true,
                  'Hidden'    : true
              },
    'showForm' : {        // Optionen auf der Webseite (true = anzeigen, false = nicht anzeigen)
                  'FormPrio'  : 1,
                  'Name'      : "showForm",
                  'Type'      : __OPTTYPES.SW,
                  'FormType'  : __OPTTYPES.SI,
                  'Permanent' : true,
                  'Default'  : false,
                  'Title'    : "$V Optionen",
                  'Action'    : __OPTACTION.NXT,
                  'Label'    : "Optionen anzeigen",
                  'Hotkey'    : 'O',
                  'AltTitle'  : "$V schlie\xDFen",
                  'AltLabel'  : "Optionen verbergen",
                  'AltHotkey' : 'O',
                  'FormLabel' : ""
              }
};


    this.setDelim(delim);
// ==================== Invarianter Abschnitt fuer Optionen ====================
    this.setBack(back);
    this.setRoot(root);
    this.setHome(home);
}


Class.define(Delims, Object, {
// Ein Satz von Logfunktionen, die je nach Loglevel zur Verfuegung stehen. Aufruf: __LOG[level](text)
              'setDelim'       : function(delim = undefined) {
const __LOG = {
                                    this.delim = delim;
                  'logFun'    : [
                                },
                                    console.error,  // [0] Alert
              'setBack'        : function(back = undefined) {
                                    console.error,  // [1] Error
                                    this.back = back;
                                    console.log,    // [2] Log: Release
                                },
                                    console.log,    // [3] Log: Info
              'setRoot'       : function(root = undefined) {
                                    console.log,    // [4] Log: Debug
                                    this.root = root;
                                    console.log,   // [5] Log: Verbose
                                },
                                    console.log,    // [6] Log: Very verbose
              'setHome'        : function(home = undefined) {
                                    console.warn    // [7] Log: Testing
                                    this.home = home;
                                ],
                                }
                  'init'     : function(win, logLevel = 1) {
          } );
                                    for (let level = 0; level < this.logFun.length; level++) {
                                        this[level] = ((level > logLevel) ? function() { } : this.logFun[level]);
                                    }
                                },
                  'stringify' : safeStringify,      // JSON.stringify
                  'changed'   : function(oldVal, newVal) {
                                    const __OLDVAL = this.stringify(oldVal);
                                    const __NEWVAL = this.stringify(newVal);
 
                                    return ((__OLDVAL !== __NEWVAL) ? __OLDVAL + " => " : "") + __NEWVAL;
                                }
              };


// ==================== Ende Abschnitt fuer Klasse Delims ====================
__LOG.init(window, __LOGLEVEL);


// ==================== Abschnitt fuer Klasse UriDelims ====================
// Kompatibilitaetsfunktion zur Ermittlung des Namens einer Funktion (falle <Function>.name nicht vorhanden ist)
if (Function.prototype.name === undefined) {
    Object.defineProperty(Function.prototype, 'name', {
            get : function() {
                      return /function ([^(\s]*)/.exec(this.toString())[1];
                  }
        });
}


// Basisklasse fuer die Verwaltung der Trennzeichen und Symbole von URIs
// Ergaenzung fuer Strings: Links oder rechts auffuellen nach Vorlage
// delim: Trennzeichen zwischen zwei Ebenen (oder Objekt/Delims mit entsprechenden Properties)
// padStr: Vorlage, z.B. "00" fuer zweistellige Zahlen
// back: (Optional) Name des relativen Vaterverzeichnisses
// padLeft: true = rechtsbuendig, false = linksbuendig
// root: (Optional) Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// clip: Abschneiden, falls zu lang
// home: (Optional) Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
// return Rechts- oder linksbuendiger String, der so lang ist wie die Vorlage
// scheme: (Optional) Trennzeichen fuer den Schema-/Protokollnamen vorne
String.prototype.pad = function(padStr, padLeft = true, clip = false) {
// host: (Optional) Prefix fuer Hostnamen hinter dem Scheme-Trenner
     const __LEN = ((clip || (padStr.length > this.length)) ? padStr.length : this.length);
// port: (Optional) Trennzeichen vor der Portangabe, falls vorhanden
// query: (Optional) Trennzeichen fuer die Query-Parameter hinter dem Pfad
// parSep: (Optional) Trennzeichen zwischen je zwei Parametern
// parAss: (Optional) Trennzwischen zwischen Key und Value
// node: (Optional) Trennzeichen fuer den Knotennamen hinten (Fragment, Kapitel)
function UriDelims(delim, back, root, home, scheme, host, port, query, qrySep, qryAss, node) {
     'use strict';


     if ((typeof delim) === 'object') {
     return (padLeft ? String(padStr + this).slice(- __LEN) : String(this + padStr).slice(0, __LEN));
        // Erster Parameter ist Objekt mit den Properties...
};
        if (scheme === undefined) {
 
            scheme = delim.scheme;
// Ersetzt in einem String {0}, {1}, ... durch die entsprechenden Parameter
        }
// arguments: Parameter, die fuer {0}, {1}, ... eingesetzt werden sollen
        if (host === undefined) {
// return Resultierender String
            host = delim.host;
String.prototype.format = function() {
        }
    const __ARGS = arguments;
        if (port === undefined) {
    return this.replace(/{(\d+)}/g, function(match, argIdx) {
            port = delim.port;
                                        const __ARG = __ARGS[argIdx];
        }
                                        return ((__ARG !== undefined) ? __ARG : match);
        if (query === undefined) {
                                    });
            query = delim.query;
};
        }
 
        if (qrySep === undefined) {
// Gibt eine Meldung in der Console aus und oeffnet ein Bestaetigungsfenster mit der Meldung
            qrySep = delim.qrySep;
// label: Eine Ueberschrift
        }
// message: Der Meldungs-Text
        if (qryAss === undefined) {
// data: Ein Wert. Ist er angegeben, wird er in der Console ausgegeben
            qryAss = delim.qryAss;
// return Liefert die Parameter zurueck
         }
function showAlert(label, message, data = undefined) {
        if (node === undefined) {
    __LOG[0](label + ": " + message);
            node = delim.node;
 
        }
    if (data !== undefined) {
         __LOG[2](data);
     }
     }


     Delims.call(this, delim, back, root, home);
     alert(label + "\n\n" + message);


     this.setScheme(scheme);
     return arguments;
    this.setHost(host);
    this.setPort(port);
    this.setQuery(query);
    this.setQrySep(qrySep);
    this.setQryAss(qryAss);
    this.setNode(node);
}
}


Class.define(UriDelims, Delims, {
// Gibt eine Meldung in der Console aus und oeffnet ein Bestaetigungsfenster
              'setScheme'      : function(scheme = undefined) {
// mit der Meldung zu einer Exception oder einer Fehlermeldung
                                    this.scheme = scheme;
// label: Eine Ueberschrift
                                },
// ex: Exception oder sonstiges Fehlerobjekt
              'setHost'        : function(host = undefined) {
// return Liefert die showAlert()-Parameter zurueck
                                    this.host = host;
function showException(label, ex) {
                                },
    if (ex && ex.message) { // Exception
              'setPort'        : function(port = undefined) {
        showAlert(label, ex.message, ex);
                                    this.port = port;
    } else {  // sonstiger Fehler
                                },
        showAlert(label, ex);
              'setQuery'      : function(query = undefined) {
    }
                                    this.query = query;
}
                                },
              'setQrySep'      : function(qrySep = undefined) {
                                    this.qrySep = qrySep;
                                },
              'setQryAss'      : function(qryAss = undefined) {
                                    this.qryAss = qryAss;
                                },
              'setNode'        : function(node = undefined) {
                                    this.node = node;
                                }
          } );


// ==================== Ende Abschnitt fuer Klasse UriDelims ====================
// Standard-Callback-Funktion fuer onRejected, also abgefangener Fehler
// in einer Promise bei Exceptions oder Fehler bzw. Rejections
// error: Parameter von reject() im Promise-Objekt, der von Promise.catch() erhalten wurde
// return Liefert die showAlert()-Parameter zurueck
function defaultCatch(error) {
    try {
        const __LABEL = `[${error.lineNumber}] ${__DBMOD.Name}`;


// ==================== Abschnitt fuer Klasse Path ====================
        if (error && error.message) {  // Exception
            return showException(__LABEL, error.message, error);
        } else {
            return showException(__LABEL, error);
        }
    } catch (ex) {
        return showException(`[${ex.lineNumber}] ${__DBMOD.Name}`, ex.message, ex);
    }
}


// Basisklasse fuer die Verwaltung eines Pfades
// ==================== Abschnitt fuer Klasse Class ====================
// homePath: Absoluter Startpfad als String
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Path(homePath, delims) {
    'use strict';


     this.dirs = [];
function Class(className, baseClass, initFun) {
    this.setDelims(delims);
    'use strict';
    this.homeDirs = this.getDirs(homePath, { 'home' : "" });
 
     try {
        const __BASE = ((baseClass !== undefined) ? baseClass : Object);
        const __BASEPROTO = (__BASE ? __BASE.prototype : undefined);
        const __BASECLASS = (__BASEPROTO ? __BASEPROTO.__class : undefined);
 
        this.className = (className || '?');
        this.baseClass = __BASECLASS;
        Object.setConst(this, 'baseProto', __BASEPROTO, false);
 
        if (! initFun) {
            const __BASEINIT = (__BASECLASS || { }).init;
 
            if (__BASEINIT) {
                initFun = function() {
                              // Basisklassen-Init aufrufen...
                              return __BASEINIT.call(this, arguments);
                          };
            } else {
                initFun = function() {
                              // Basisklassen-Init fehlt (und Basisklasse ist nicht Object)...
                              return false;
                          };
            }
        }
 
        console.assert((__BASE === null) || ((typeof __BASE) === 'function'), "No function:", __BASE);
        console.assert((typeof initFun) === 'function', "Not a function:", initFun);


    this.home();
        this.init = initFun;
    } catch (ex) {
        showAlert('[' + ex.lineNumber + "] Error in Class " + className, ex.message, ex);
    }
}
}


Class.define(Path, Object, {
Class.define = function(subClass, baseClass, members = undefined, initFun = undefined, createProto = true) {
                  'root'           : function() {
        return (subClass.prototype = subClass.subclass(baseClass, members, initFun, createProto));
                                        this.dirs.splice(0, this.dirs.length);
    };
 
Object.setConst = function(obj, item, value, config) {
        return Object.defineProperty(obj, item, {
                        enumerable  : false,
                        configurable : (config || true),
                        writable    : false,
                        value        : value
                    });
    };
 
Object.setConst(Object.prototype, 'subclass', function(baseClass, members, initFun, createProto) {
        'use strict';
 
        try {
            const __MEMBERS = (members || { });
            const __CREATEPROTO = ((createProto === undefined) ? true : createProto);
 
            console.assert((typeof this) === 'function', "Not a function:", this);
            console.assert((typeof __MEMBERS) === 'object', "Not an object:", __MEMBERS);
 
            const __CLASS = new Class(this.name || __MEMBERS.__name, baseClass, initFun || __MEMBERS.__init);
            const __PROTO = (__CREATEPROTO ? Object.create(__CLASS.baseProto) : this.prototype);
 
            for (let item in __MEMBERS) {
                if ((item !== '__name') && (item !== '__init')) {
                    Object.setConst(__PROTO, item, __MEMBERS[item]);
                }
            }
 
            Object.setConst(__PROTO, '__class', __CLASS, ! __CREATEPROTO);
 
            return __PROTO;
        } catch (ex) {
            showAlert('[' + ex.lineNumber + "] Error in subclassing", ex.message, ex);
        }
    }, false);
 
Class.define(Object, null, {
                    '__init'      : function() {
                                        // Oberstes Basisklassen-Init...
                                        return true;
                                     },
                                     },
                  'home'           : function() {
                    'getClass'     : function() {
                                         this.dirs = this.homeDirs.slice();
                                         return this.__class;
                                     },
                                     },
                  'up'             : function() {
                    'getClassName' : function() {
                                         this.dirs.pop();
                                         const __CLASS = this.getClass();
 
                                        return (__CLASS ? __CLASS.getName() : undefined);
                                     },
                                     },
                  'down'           : function(subDir) {
                    'setConst'     : function(item, value, config = undefined) {
                                        this.dirs.push(subDir);
                                         return Object.setConst(this, item, value, config);
                                    },
                                     }
                  'setDelims'      : function(delims = undefined) {
                }, undefined, false);
                                         this.setConst('delims', new Delims(delims));
 
                                     },
Class.define(Function, Object);
                  'setDelim'      : function(delim = undefined) {
 
                                        this.delims.setDelim(delim || '/');
Class.define(Class, Object, {
                                    },
                    'getName'     : function() {
                  'setBackDelim'  : function(backDelim = undefined) {
                                         return this.className;
                                        this.delims.setBack(backDelim || "..");
                                     }
                                    },
                });
                  'setRootDelim'  : function(rootDelim = undefined) {
 
                                        this.delims.setRoot(rootDelim || "");
// ==================== Ende Abschnitt fuer Klasse Class ====================
                                    },
 
                  'setHomeDelim'   : function(homeDelim = undefined) {
// ==================== Abschnitt fuer Klasse Delims ====================
                                         this.delims.setHome(homeDelim || '~');
 
                                     },
// Basisklasse fuer die Verwaltung der Trennzeichen und Symbole von Pfaden
                  'setSchemeDelim' : function(schemeDelim = undefined) {
// delim: Trennzeichen zwischen zwei Ebenen (oder Objekt/Delims mit entsprechenden Properties)
                                        this.delims.setScheme(schemeDelim || ':');
// back: (Optional) Name des relativen Vaterverzeichnisses
                                    },
// root: (Optional) Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
                  'setHostDelim'  : function(hostDelim = undefined) {
// home: (Optional) Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
                                        this.delims.setHost(hostDelim || '//');
function Delims(delim, back, root, home) {
                                    },
    'use strict';
                  'setPortDelim'  : function(portDelim = undefined) {
                                        this.delims.setHost(portDelim || ':');
                                    },
                  'setQueryDelim'  : function(queryDelim = undefined) {
                                        this.delims.setQuery(queryDelim || '?');
                                    },
                  'setParSepDelim' : function(parSepDelim = undefined) {
                                        this.delims.setParSep(parSepDelim || '&');
                                    },
                  'setParAssDelim' : function(parAssDelim = undefined) {
                                        this.delims.setParAss(parAssDelim || '=');
                                    },
                  'setNodeDelim'  : function(nodeDelim = undefined) {
                                        this.delims.setNode(nodeDelim || '#');
                                    },
                  'getLeaf'        : function(dirs = undefined) {
                                        const __DIRS = (dirs || this.dirs);


                                        return ((__DIRS && __DIRS.length) ? __DIRS.slice(-1)[0] : "");
    if ((typeof delim) === 'object') {
                                    },
        // Erster Parameter ist Objekt mit den Properties...
                  'getPath'        : function(dirs = undefined, delims = undefined) {
        if (back === undefined) {
                                        const __DELIMS = new Delims(delims);
            back = delim.back;
                                        const __DELIM = (__DELIMS.delim || this.delims.delim);
        }
                                        const __ROOTDELIM = ((__DELIMS.root !== undefined) ? __DELIMS.root : this.delims.root);
        if (root === undefined) {
                                        const __DIRS = (dirs || this.dirs);
            root = delim.root;
        }
        if (home === undefined) {
            home = delim.home;
        }
        delim = delim.delim;
    }


                                        return __ROOTDELIM + __DELIM + __DIRS.join(__DELIM);
    this.setDelim(delim);
                                    },
    this.setBack(back);
                  'getDirs'        : function(path = undefined, delims = undefined) {
    this.setRoot(root);
                                        const __DELIMS = new Delims(delims);
    this.setHome(home);
                                        const __DELIM = (__DELIMS.delim || this.delims.delim);
}
                                        const __ROOTDELIM = ((__DELIMS.root !== undefined) ? __DELIMS.root : this.delims.root);
                                        const __HOMEDELIM = ((__DELIMS.home !== undefined) ? __DELIMS.home : this.delims.home);
                                        const __DIRS = (path ? path.split(__DELIM) : []);
                                        const __FIRST = __DIRS[0];


                                        if (__FIRST && (__FIRST !== __ROOTDELIM) && (__FIRST !== __HOMEDELIM)) {
Class.define(Delims, Object, {
                                            showAlert("Kein absoluter Pfad", this.getPath(__DIRS), this);
              'setDelim'      : function(delim = undefined) {
                                        }
                                    this.delim = delim;
                                },
              'setBack'        : function(back = undefined) {
                                    this.back = back;
                                },
              'setRoot'        : function(root = undefined) {
                                    this.root = root;
                                },
              'setHome'        : function(home = undefined) {
                                    this.home = home;
                                }
          });


                                        return __DIRS.slice(1);
// ==================== Ende Abschnitt fuer Klasse Delims ====================
                                    }
                } );


// ==================== Ende Abschnitt fuer Klasse Path ====================
// ==================== Abschnitt fuer Klasse UriDelims ====================


// ==================== Abschnitt fuer Klasse URI ====================
// Basisklasse fuer die Verwaltung der Trennzeichen und Symbole von URIs
 
// delim: Trennzeichen zwischen zwei Ebenen (oder Objekt/Delims mit entsprechenden Properties)
// Basisklasse fuer die Verwaltung einer URI/URL
// back: (Optional) Name des relativen Vaterverzeichnisses
// homePath: Absoluter Startpfad als String
// root: (Optional) Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// home: (Optional) Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
// 'delim': Trennzeichen zwischen zwei Ebenen
// scheme: (Optional) Trennzeichen fuer den Schema-/Protokollnamen vorne
// 'back': Name des relativen Vaterverzeichnisses
// host: (Optional) Prefix fuer Hostnamen hinter dem Scheme-Trenner
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// port: (Optional) Trennzeichen vor der Portangabe, falls vorhanden
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
// query: (Optional) Trennzeichen fuer die Query-Parameter hinter dem Pfad
function URI(homePath, delims) {
// parSep: (Optional) Trennzeichen zwischen je zwei Parametern
// parAss: (Optional) Trennzwischen zwischen Key und Value
// node: (Optional) Trennzeichen fuer den Knotennamen hinten (Fragment, Kapitel)
function UriDelims(delim, back, root, home, scheme, host, port, query, qrySep, qryAss, node) {
     'use strict';
     'use strict';


     Path.call(this);
     if ((typeof delim) === 'object') {
 
        // Erster Parameter ist Objekt mit den Properties...
    const __HOSTPORT = this.getHostPort(homePath);
        if (scheme === undefined) {
 
            scheme = delim.scheme;
    this.scheme = this.getSchemePrefix(homePath);
        }
    this.host = __HOSTPORT.host;
        if (host === undefined) {
    this.port = this.parseValue(__HOSTPORT.port);
            host = delim.host;
    this.query = this.parseQuery(this.getQueryString(homePath));
        }
    this.node = this.getNodeSuffix(homePath);
        if (port === undefined) {
 
            port = delim.port;
     this.homeDirs = this.getDirs(homePath, { 'home' : "" });
        }
        if (query === undefined) {
            query = delim.query;
        }
        if (qrySep === undefined) {
            qrySep = delim.qrySep;
        }
        if (qryAss === undefined) {
            qryAss = delim.qryAss;
        }
        if (node === undefined) {
            node = delim.node;
        }
     }
 
    Delims.call(this, delim, back, root, home);


     this.home();
     this.setScheme(scheme);
    this.setHost(host);
    this.setPort(port);
    this.setQuery(query);
    this.setQrySep(qrySep);
    this.setQryAss(qryAss);
    this.setNode(node);
}
}


Class.define(URI, Path, {
Class.define(UriDelims, Delims, {
              'setDelims'         : function() {
              'setScheme'     : function(scheme = undefined) {
                                        this.setConst('delims', new UriDelims('/', "..", "", '~', ':', "//", ':', '?', '&', '=', '#'));
                                    this.scheme = scheme;
                                    },
                                },
              'setSchemeDelim'   : function(schemeDelim = undefined) {
              'setHost'       : function(host = undefined) {
                                        this.delims.setScheme(schemeDelim || ':');
                                    this.host = host;
                                    },
                                },
              'setQueryDelim'     : function(queryDelim = undefined) {
              'setPort'       : function(port = undefined) {
                                        this.delims.setQuery(queryDelim || '?');
                                    this.port = port;
                                    },
                                },
              'setParSepDelim'   : function(parSepDelim = undefined) {
              'setQuery'       : function(query = undefined) {
                                         this.delims.setParSep(parSepDelim || '&');
                                    this.query = query;
                                },
              'setQrySep'     : function(qrySep = undefined) {
                                    this.qrySep = qrySep;
                                },
              'setQryAss'     : function(qryAss = undefined) {
                                    this.qryAss = qryAss;
                                },
              'setNode'       : function(node = undefined) {
                                    this.node = node;
                                }
          });
 
// ==================== Ende Abschnitt fuer Klasse UriDelims ====================
 
// ==================== Abschnitt fuer Klasse Path ====================
 
// Basisklasse fuer die Verwaltung eines Pfades
// homePath: Absoluter Startpfad als String
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Path(homePath, delims) {
    'use strict';
 
    this.dirs = [];
    this.setDelims(delims);
    this.homeDirs = this.getDirs(homePath, { 'home' : "" });
 
    this.home();
}
 
Class.define(Path, Object, {
                  'root'           : function() {
                                         this.dirs.splice(0, this.dirs.length);
                                     },
                                     },
              'setParAssDelim'   : function(parAssDelim = undefined) {
                  'home'           : function() {
                                         this.delims.setParAss(parAssDelim || '=');
                                         this.dirs = this.homeDirs.slice();
                                     },
                                     },
              'setNodeDelim'     : function(nodeDelim = undefined) {
                  'up'             : function() {
                                         this.delims.setNode(nodeDelim || '#');
                                         this.dirs.pop();
                                     },
                                     },
              'getServerPath'     : function(path = undefined) {
                  'down'           : function(subDir) {
                                         return this.stripHostPort(this.stripQueryString(this.stripNodeSuffix(this.stripSchemePrefix(path))));
                                         this.dirs.push(subDir);
                                     },
                                     },
              'getHostPort'       : function(path = undefined) {
                  'setDelims'     : function(delims = undefined) {
                                         const __HOSTDELIM = this.delims.host;
                                         this.setConst('delims', new Delims(delims));
                                        const __PORTDELIM = this.delims.port;
                                        const __ROOTDELIM = this.delims.root + this.delims.delim;
                                        const __NOSCHEME = this.stripSchemePrefix(path);
                                        const __INDEXHOST = (__NOSCHEME ? __NOSCHEME.indexOf(__HOSTDELIM) : -1);
                                        const __PATH = (~ __INDEXHOST) ? __NOSCHEME.substring(__INDEXHOST + __HOSTDELIM.length) : __NOSCHEME;
                                        const __INDEXHOSTPORT = (__PATH ? __PATH.indexOf(__ROOTDELIM) : -1);
                                        const __HOSTPORT = (~ __INDEXHOSTPORT) ? __PATH.substring(0, __INDEXHOSTPORT) : undefined;
                                        const __INDEXPORT = (__HOSTPORT ? __HOSTPORT.indexOf(__PORTDELIM) : -1);
                                        const __HOST = (~ __INDEXPORT) ? __HOSTPORT.substring(0, __INDEXPORT) : __HOSTPORT;
                                        const __PORT = (~ __INDEXPORT) ? __HOSTPORT.substring(__INDEXPORT + __PORTDELIM.length) : undefined;
 
                                        return {
                                                    'host' : __HOST,
                                                    'port' : __PORT
                                                };
                                     },
                                     },
              'stripHostPort'     : function(path = undefined) {
                  'setDelim'       : function(delim = undefined) {
                                         const __HOSTDELIM = this.delims.host;
                                         this.delims.setDelim(delim || '/');
                                        const __ROOTDELIM = this.delims.root + this.delims.delim;
                                        const __INDEXHOST = (path ? path.indexOf(__HOSTDELIM) : -1);
                                        const __PATH = (~ __INDEXHOST) ? path.substring(__INDEXHOST + __HOSTDELIM.length) : path;
                                        const __INDEXHOSTPORT = (__PATH ? __PATH.indexOf(__ROOTDELIM) : -1);
 
                                        return (~ __INDEXHOSTPORT) ? __PATH.substring(__INDEXHOSTPORT) : __PATH;
                                     },
                                     },
              'getSchemePrefix'  : function(path = undefined) {
                  'setBackDelim'  : function(backDelim = undefined) {
                                         const __SCHEMEDELIM = this.delims.scheme;
                                         this.delims.setBack(backDelim || "..");
                                        const __INDEXSCHEME = (path ? path.indexOf(__SCHEMEDELIM) : -1);
 
                                        return (~ __INDEXSCHEME) ? path.substring(0, __INDEXSCHEME) : undefined;
                                     },
                                     },
              'stripSchemePrefix' : function(path = undefined) {
                  'setRootDelim'   : function(rootDelim = undefined) {
                                         const __SCHEMEDELIM = this.delims.scheme;
                                         this.delims.setRoot(rootDelim || "");
                                        const __INDEXSCHEME = (path ? path.indexOf(__SCHEMEDELIM) : -1);
 
                                        return (~ __INDEXSCHEME) ? path.substring(__INDEXSCHEME + __INDEXSCHEME.length) : path;
                                     },
                                     },
              'getNodeSuffix'     : function(path = undefined) {
                  'setHomeDelim'   : function(homeDelim = undefined) {
                                         const __NODEDELIM = this.delims.node;
                                         this.delims.setHome(homeDelim || '~');
                                         const __INDEXNODE = (path ? path.lastIndexOf(__NODEDELIM) : -1);
                                    },
 
                  'setSchemeDelim' : function(schemeDelim = undefined) {
                                        return (~ __INDEXNODE) ? path.substring(__INDEXNODE + __NODEDELIM.length) : undefined;
                                         this.delims.setScheme(schemeDelim || ':');
                                    },
                  'setHostDelim'  : function(hostDelim = undefined) {
                                        this.delims.setHost(hostDelim || '//');
                                    },
                  'setPortDelim'  : function(portDelim = undefined) {
                                        this.delims.setHost(portDelim || ':');
                                     },
                                     },
              'stripNodeSuffix'   : function(path = undefined) {
                  'setQueryDelim' : function(queryDelim = undefined) {
                                         const __NODEDELIM = this.delims.node;
                                         this.delims.setQuery(queryDelim || '?');
                                        const __INDEXNODE = (path ? path.lastIndexOf(__NODEDELIM) : -1);
 
                                        return (~ __INDEXNODE) ? path.substring(0, __INDEXNODE) : path;
                                     },
                                     },
              'getQueryString'   : function(path = undefined) {
                  'setParSepDelim' : function(parSepDelim = undefined) {
                                         const __QUERYDELIM = this.delims.query;
                                         this.delims.setParSep(parSepDelim || '&');
                                        const __PATH = this.stripNodeSuffix(path);
                                        const __INDEXQUERY = (__PATH ? __PATH.indexOf(__QUERYDELIM) : -1);
 
                                        return (~ __INDEXQUERY) ? __PATH.substring(__INDEXQUERY + __QUERYDELIM.length) : undefined;
                                     },
                                     },
              'stripQueryString' : function(path = undefined) {
                  'setParAssDelim' : function(parAssDelim = undefined) {
                                         const __QUERYDELIM = this.delims.query;
                                         this.delims.setParAss(parAssDelim || '=');
                                        const __INDEXQUERY = (path ? path.indexOf(__QUERYDELIM) : -1);
 
                                        return (~ __INDEXQUERY) ? path.substring(0, __INDEXQUERY) : path;
                                     },
                                     },
              'formatParams'     : function(params, formatFun, delim = ' ', assign = '=') {
                  'setNodeDelim'   : function(nodeDelim = undefined) {
                                         const __PARAMS = [];
                                         this.delims.setNode(nodeDelim || '#');
 
                                        for (let param in params) {
                                            __PARAMS.push(param + assign + formatFun(params[param]));
                                        }
 
                                        return __PARAMS.join(delim);
                                     },
                                     },
              'parseParams'       : function(params, parseFun, delim = ' ', assign = '=') {
                  'getLeaf'       : function(dirs = undefined) {
                                         const __RET = { };
                                         const __DIRS = (dirs || this.dirs);


                                         if (params) {
                                         return ((__DIRS && __DIRS.length) ? __DIRS.slice(-1)[0] : "");
                                            const __PARAMS = params.split(delim);
                                    },
                  'getPath'        : function(dirs = undefined, delims = undefined) {
                                        const __DELIMS = new Delims(delims);
                                        const __DELIM = (__DELIMS.delim || this.delims.delim);
                                        const __ROOTDELIM = ((__DELIMS.root !== undefined) ? __DELIMS.root : this.delims.root);
                                        const __DIRS = (dirs || this.dirs);


                                            for (let index = 0; index < __PARAMS.length; index++) {
                                        return __ROOTDELIM + __DELIM + __DIRS.join(__DELIM);
                                                const __PARAM = __PARAMS[index];
                                    },
                  'getDirs'        : function(path = undefined, delims = undefined) {
                                        const __DELIMS = new Delims(delims);
                                        const __DELIM = (__DELIMS.delim || this.delims.delim);
                                        const __ROOTDELIM = ((__DELIMS.root !== undefined) ? __DELIMS.root : this.delims.root);
                                        const __HOMEDELIM = ((__DELIMS.home !== undefined) ? __DELIMS.home : this.delims.home);
                                        const __DIRS = (path ? path.split(__DELIM) : []);
                                        const __FIRST = __DIRS[0];


                                                if (__PARAM) {
                                        if (__FIRST && (__FIRST !== __ROOTDELIM) && (__FIRST !== __HOMEDELIM)) {
                                                    const __INDEX = __PARAM.indexOf(assign);
                                            showAlert("Kein absoluter Pfad", this.getPath(__DIRS), this);
                                                    const __KEY = (~ __INDEX) ? __PARAM.substring(0, __INDEX) : __PARAM;
                                                    const __VAL = (~ __INDEX) ? parseFun(__PARAM.substring(__INDEX + assign.length)) : true;
 
                                                    __RET[__KEY] = __VAL;
                                                }
                                            }
                                         }
                                         }


                                         return __RET;
                                         return __DIRS.slice(1);
                                    },
                                     }
              'rawValue'          : function(value) {
                });
                                        return value;
                                     },
              'parseValue'        : function(value) {
                                        const __VALUE = Number(value);


                                        if (__VALUE == value) // schwacher Vergleich true, also Number
// ==================== Ende Abschnitt fuer Klasse Path ====================
                                            return __VALUE;
 
                                        } else {
// ==================== Abschnitt fuer Klasse URI ====================
                                            const __LOWER = (value ? value.toLowerCase() : undefined);
 
// Basisklasse fuer die Verwaltung einer URI/URL
// homePath: Absoluter Startpfad als String
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function URI(homePath, delims) {
    'use strict';
 
    Path.call(this);


                                            if ((__LOWER === "true") || (__LOWER === "false")) {
    const __HOSTPORT = this.getHostPort(homePath);
                                                return (value === "true");
                                            }
                                        }


                                        return value;
    this.scheme = this.getSchemePrefix(homePath);
                                    },
    this.host = __HOSTPORT.host;
              'getQuery'          : function(delims = { }) {
    this.port = this.parseValue(__HOSTPORT.port);
                                        const __QRYSEP = ((delims.qrySep !== undefined) ? delims.qrySep : this.delims.qrySep);
    this.query = this.parseQuery(this.getQueryString(homePath));
                                        const __QRYASS = ((delims.qryAss !== undefined) ? delims.qryAss : this.delims.qryAss);
    this.node = this.getNodeSuffix(homePath);


                                        return this.formatParams(this.query, this.rawValue, __QRYSEP, __QRYASS);
    this.homeDirs = this.getDirs(homePath, { 'home' : "" });
                                    },
              'parseQuery'       : function(path = undefined, delims = { }) {
                                        const __QRYSEP = ((delims.qrySep !== undefined) ? delims.qrySep : this.delims.qrySep);
                                        const __QRYASS = ((delims.qryAss !== undefined) ? delims.qryAss : this.delims.qryAss);


                                         return this.parseParams(path, this.parseValue, __QRYSEP, __QRYASS);
    this.home();
                                     },
}
               'setQuery'         : function(query) {
 
                                         this.query = query;
Class.define(URI, Path, {
                                     },
              'setDelims'        : function() {
               'setQueryPar'       : function(key, value) {
                                         this.setConst('delims', new UriDelims('/', "..", "", '~', ':', "//", ':', '?', '&', '=', '#'));
                                         this.query[key] = value;
                                     },
               'setSchemeDelim'   : function(schemeDelim = undefined) {
                                         this.delims.setScheme(schemeDelim || ':');
                                     },
               'setQueryDelim'     : function(queryDelim = undefined) {
                                         this.delims.setQuery(queryDelim || '?');
                                     },
                                     },
               'getQueryPar'       : function(key) {
               'setParSepDelim'   : function(parSepDelim = undefined) {
                                         return this.query[key];
                                         this.delims.setParSep(parSepDelim || '&');
                                     },
                                     },
               'getPath'           : function(dirs = undefined, delims = undefined) {
               'setParAssDelim'   : function(parAssDelim = undefined) {
                                         const __DELIMS = new UriDelims(delims);
                                         this.delims.setParAss(parAssDelim || '=');
                                        const __SCHEMEDELIM = ((__DELIMS.scheme !== undefined) ? __DELIMS.scheme : this.delims.scheme);
                                        const __HOSTDELIM = ((__DELIMS.host !== undefined) ? __DELIMS.host : this.delims.host);
                                        const __PORTDELIM = ((__DELIMS.port !== undefined) ? __DELIMS.port : this.delims.port);
                                        const __QUERYDELIM = ((__DELIMS.query !== undefined) ? __DELIMS.query : this.delims.query);
                                        const __NODEDELIM = ((__DELIMS.node !== undefined) ? __DELIMS.node : this.delims.node);
                                        const __SCHEMENAME = this.scheme;
                                        const __SCHEME = (__SCHEMENAME ? __SCHEMENAME + __SCHEMEDELIM : "");
                                        const __HOSTNAME = this.host;
                                        const __HOST = (__HOSTNAME ? __HOSTDELIM + __HOSTNAME : "");
                                        const __PORTNR = this.port;
                                        const __PORT = ((__HOSTNAME && __PORTNR) ? __PORTDELIM + __PORTNR : "");
                                        const __QUERYSTR = this.getQuery();
                                        const __QUERY = (__QUERYSTR ? __QUERYDELIM + __QUERYSTR : "");
                                        const __NODENAME = this.node;
                                        const __NODE = (__NODENAME ? __NODEDELIM + __NODENAME : "");
 
                                        return __SCHEME + __HOST + __PORT + Path.prototype.getPath.call(this, dirs, delims) + __QUERY + __NODE;
                                     },
                                     },
               'getDirs'           : function(path = undefined, delims = undefined) {
               'setNodeDelim'     : function(nodeDelim = undefined) {
                                         const __PATH = this.getServerPath(path);
                                        this.delims.setNode(nodeDelim || '#');
 
                                    },
                                         return Path.prototype.getDirs.call(this, __PATH);
              'getServerPath'    : function(path = undefined) {
                                    }
                                        return this.stripHostPort(this.stripQueryString(this.stripNodeSuffix(this.stripSchemePrefix(path))));
          } );
                                    },
              'getHostPort'      : function(path = undefined) {
                                         const __HOSTDELIM = this.delims.host;
                                        const __PORTDELIM = this.delims.port;
                                        const __ROOTDELIM = this.delims.root + this.delims.delim;
                                        const __NOSCHEME = this.stripSchemePrefix(path);
                                        const __INDEXHOST = (__NOSCHEME ? __NOSCHEME.indexOf(__HOSTDELIM) : -1);
                                         const __PATH = ((~ __INDEXHOST) ? __NOSCHEME.substring(__INDEXHOST + __HOSTDELIM.length) : __NOSCHEME);
                                        const __INDEXHOSTPORT = (__PATH ? __PATH.indexOf(__ROOTDELIM) : -1);
                                        const __HOSTPORT = ((~ __INDEXHOSTPORT) ? __PATH.substring(0, __INDEXHOSTPORT) : undefined);
                                        const __INDEXPORT = (__HOSTPORT ? __HOSTPORT.indexOf(__PORTDELIM) : -1);
                                        const __HOST = ((~ __INDEXPORT) ? __HOSTPORT.substring(0, __INDEXPORT) : __HOSTPORT);
                                        const __PORT = ((~ __INDEXPORT) ? __HOSTPORT.substring(__INDEXPORT + __PORTDELIM.length) : undefined);


// ==================== Ende Abschnitt fuer Klasse URI ====================
                                        return {
                                                    'host' : __HOST,
                                                    'port' : __PORT
                                                };
                                    },
              'stripHostPort'    : function(path = undefined) {
                                        const __HOSTDELIM = this.delims.host;
                                        const __ROOTDELIM = this.delims.root + this.delims.delim;
                                        const __INDEXHOST = (path ? path.indexOf(__HOSTDELIM) : -1);
                                        const __PATH = ((~ __INDEXHOST) ? path.substring(__INDEXHOST + __HOSTDELIM.length) : path);
                                        const __INDEXHOSTPORT = (__PATH ? __PATH.indexOf(__ROOTDELIM) : -1);


// ==================== Abschnitt fuer Klasse Directory ====================
                                        return ((~ __INDEXHOSTPORT) ? __PATH.substring(__INDEXHOSTPORT) : __PATH);
                                    },
              'getSchemePrefix'  : function(path = undefined) {
                                        const __SCHEMEDELIM = this.delims.scheme;
                                        const __INDEXSCHEME = (path ? path.indexOf(__SCHEMEDELIM) : -1);


// Basisklasse fuer eine Verzeichnisstruktur
                                        return ((~ __INDEXSCHEME) ? path.substring(0, __INDEXSCHEME) : undefined);
// homePath: Absoluter Startpfad als String
                                    },
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
              'stripSchemePrefix' : function(path = undefined) {
// 'delim': Trennzeichen zwischen zwei Ebenen
                                        const __SCHEMEDELIM = this.delims.scheme;
// 'back': Name des relativen Vaterverzeichnisses
                                        const __INDEXSCHEME = (path ? path.indexOf(__SCHEMEDELIM) : -1);
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Directory(homePath, delims) {
    'use strict';


     Path.call(this, homePath, delims);
                                        return ((~ __INDEXSCHEME) ? path.substring(__INDEXSCHEME + __INDEXSCHEME.length) : path);
}
                                    },
              'getNodeSuffix'     : function(path = undefined) {
                                        const __NODEDELIM = this.delims.node;
                                        const __INDEXNODE = (path ? path.lastIndexOf(__NODEDELIM) : -1);


Class.define(Directory, Path, {
                                        return ((~ __INDEXNODE) ? path.substring(__INDEXNODE + __NODEDELIM.length) : undefined);
                    'chDir' : function(subDir = undefined) {
                                    },
                                  if (subDir === undefined) {
              'stripNodeSuffix'   : function(path = undefined) {
                                      this.root();
                                        const __NODEDELIM = this.delims.node;
                                  } else if ((typeof subDir) === 'object') {
                                        const __INDEXNODE = (path ? path.lastIndexOf(__NODEDELIM) : -1);
                                      for (let sub of subDir) {
                                          this.chDir(sub);
                                      }
                                  } else {
                                      if (subDir === this.delims.home) {
                                          this.home();
                                      } else if (subDir === this.delims.back) {
                                          this.up();
                                      } else {
                                          this.down(subDir);
                                      }
                                  }
                              },
                    'pwd'  : function() {
                                  return this.getPath();
                              }
                } );


// ==================== Ende Abschnitt fuer Klasse Directory ====================
                                        return ((~ __INDEXNODE) ? path.substring(0, __INDEXNODE) : path);
                                    },
              'getQueryString'    : function(path = undefined) {
                                        const __QUERYDELIM = this.delims.query;
                                        const __PATH = this.stripNodeSuffix(path);
                                        const __INDEXQUERY = (__PATH ? __PATH.indexOf(__QUERYDELIM) : -1);


// ==================== Abschnitt fuer Klasse ObjRef ====================
                                        return ((~ __INDEXQUERY) ? __PATH.substring(__INDEXQUERY + __QUERYDELIM.length) : undefined);
                                    },
              'stripQueryString'  : function(path = undefined) {
                                        const __QUERYDELIM = this.delims.query;
                                        const __INDEXQUERY = (path ? path.indexOf(__QUERYDELIM) : -1);


// Basisklasse fuer eine Objekt-Referenz
                                        return ((~ __INDEXQUERY) ? path.substring(0, __INDEXQUERY) : path);
function ObjRef(rootObj) {
                                    },
    'use strict';
              'formatParams'      : function(params, formatFun, delim = ' ', assign = '=') {
                                        const __PARAMS = [];


    Directory.call(this, undefined, new Delims('/', "..", '/', '~'));
                                        for (let param in params) {
                                            __PARAMS.push(param + assign + formatFun(params[param]));
                                        }


    this.setConst('rootObj', rootObj);  // Wichtig: Verweis nicht verfolgen! Gefahr durch Zyklen!
                                        return __PARAMS.join(delim);
}
                                    },
              'parseParams'       : function(params, parseFun, delim = ' ', assign = '=') {
                                        const __RET = { };


Class.define(ObjRef, Directory, {
                                        if (params) {
                    'valueOf' : function() {
                                            const __PARAMS = params.split(delim);
                                    let ret = this.rootObj;


                                    for (let name of this.dirs) {
                                            for (let index = 0; index < __PARAMS.length; index++) {
                                        if (ret === undefined) {
                                                const __PARAM = __PARAMS[index];
                                            break;
                                        }
                                        ret = ret[name];
                                    }


                                    return ret;
                                                if (__PARAM) {
                                }
                                                    const __INDEX = __PARAM.indexOf(assign);
                } );
                                                    const __KEY = ((~ __INDEX) ? __PARAM.substring(0, __INDEX) : __PARAM);
                                                    const __VAL = ((~ __INDEX) ? parseFun(__PARAM.substring(__INDEX + assign.length)) : true);


// ==================== Ende Abschnitt fuer Klasse ObjRef ====================
                                                    __RET[__KEY] = __VAL;
                                                }
                                            }
                                        }


// ==================== Abschnitt fuer diverse Utilities ====================
                                        return __RET;
 
                                    },
// Gibt einen Wert zurueck. Ist dieser nicht definiert oder null, wird ein Alternativwert geliefert
              'rawValue'          : function(value) {
// value: Ein Wert. Ist dieser nicht undefined oder null, wird er zurueckgeliefert (oder retValue)
                                        return value;
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
                                    },
// retValue: Falls definiert, Rueckgabe-Wert fuer den Fall, dass value nicht undefined oder null ist
              'parseValue'        : function(value) {
// return Der Wert. Sind weder value noch defValue definiert, dann undefined
                                        const __VALUE = Number(value);
function getValue(value, defValue = undefined, retValue = undefined) {
    return ((value === undefined) || (value === null)) ? defValue : (retValue === undefined) ? value : retValue;
}


// Gibt einen Wert zurueck. Ist dieser nicht definiert, wird ein Alternativwert geliefert
                                        if (__VALUE == value) {  // schwacher Vergleich true, also Number
// value: Ein Wert. Ist dieser definiet und in den Grenzen, wird er zurueckgeliefert
                                            return __VALUE;
// minValue: Untere Grenze fuer den Wert, falls angegeben
                                        } else {
// minValue: Obere Grenze fuer den Wert, falls angegeben
                                            const __LOWER = (value ? value.toLowerCase() : undefined);
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist oder der Wert ausserhalb liegt
// return Der Wert. Sind weder value (in den Grenzen) noch defValue definiert, dann undefined
function getValueIn(value, minValue = undefined, maxValue = undefined, defValue = undefined) {
    const __VALUE = getValue(value, defValue);


    if ((minValue !== undefined) && (__VALUE < minValue)) {
                                            if ((__LOWER === 'true') || (__LOWER === 'false')) {
        return defValue;
                                                return (value === 'true');
    }
                                            }
    if ((maxValue !== undefined) && (__VALUE > maxValue)) {
                                        }
        return defValue;
    }


    return __VALUE;
                                        return value;
}
                                    },
              'getQuery'          : function(delims = { }) {
                                        const __QRYSEP = ((delims.qrySep !== undefined) ? delims.qrySep : this.delims.qrySep);
                                        const __QRYASS = ((delims.qryAss !== undefined) ? delims.qryAss : this.delims.qryAss);


// Ermittelt den naechsten Wert aus einer Array-Liste
                                        return this.formatParams(this.query, this.rawValue, __QRYSEP, __QRYASS);
// arr: Array-Liste mit den moeglichen Werte
                                    },
// value: Vorher gesetzter Wert
              'parseQuery'        : function(path = undefined, delims = { }) {
// return Naechster Wert in der Array-Liste
                                        const __QRYSEP = ((delims.qrySep !== undefined) ? delims.qrySep : this.delims.qrySep);
function getNextValue(arr, value) {
                                        const __QRYASS = ((delims.qryAss !== undefined) ? delims.qryAss : this.delims.qryAss);
    const __POS = arr.indexOf(value) + 1;


    return arr[getValueIn(__POS, 0, arr.length - 1, 0)];
                                        return this.parseParams(path, this.parseValue, __QRYSEP, __QRYASS);
}
                                    },
              'setQuery'          : function(query) {
                                        this.query = query;
                                    },
              'setQueryPar'      : function(key, value) {
                                        this.query[key] = value;
                                    },
              'getQueryPar'      : function(key) {
                                        return this.query[key];
                                    },
              'getPath'          : function(dirs = undefined, delims = undefined) {
                                        const __DELIMS = new UriDelims(delims);
                                        const __SCHEMEDELIM = ((__DELIMS.scheme !== undefined) ? __DELIMS.scheme : this.delims.scheme);
                                        const __HOSTDELIM = ((__DELIMS.host !== undefined) ? __DELIMS.host : this.delims.host);
                                        const __PORTDELIM = ((__DELIMS.port !== undefined) ? __DELIMS.port : this.delims.port);
                                        const __QUERYDELIM = ((__DELIMS.query !== undefined) ? __DELIMS.query : this.delims.query);
                                        const __NODEDELIM = ((__DELIMS.node !== undefined) ? __DELIMS.node : this.delims.node);
                                        const __SCHEMENAME = this.scheme;
                                        const __SCHEME = (__SCHEMENAME ? __SCHEMENAME + __SCHEMEDELIM : "");
                                        const __HOSTNAME = this.host;
                                        const __HOST = (__HOSTNAME ? __HOSTDELIM + __HOSTNAME : "");
                                        const __PORTNR = this.port;
                                        const __PORT = ((__HOSTNAME && __PORTNR) ? __PORTDELIM + __PORTNR : "");
                                        const __QUERYSTR = this.getQuery();
                                        const __QUERY = (__QUERYSTR ? __QUERYDELIM + __QUERYSTR : "");
                                        const __NODENAME = this.node;
                                        const __NODE = (__NODENAME ? __NODEDELIM + __NODENAME : "");


// Gibt ein Produkt zurueck. Ist einer der Multiplikanten nicht definiert, wird ein Alternativwert geliefert
                                        return __SCHEME + __HOST + __PORT + Path.prototype.getPath.call(this, dirs, delims) + __QUERY + __NODE;
// valueA: Ein Multipliksnt. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
                                    },
// valueB: Ein Multipliksnt. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
              'getDirs'          : function(path = undefined, delims = undefined) {
// digits: Anzahl der Stellen nach dem Komma fuer das Produkt (Default: 0)
                                        const __PATH = this.getServerPath(path);
// defValue: Default-Wert fuer den Fall, dass ein Multiplikant nicht gesetzt ist (Default: NaN)
 
// return Das Produkt auf digits Stellen genau. Ist dieses nicht definiert, dann defValue
                                        return Path.prototype.getDirs.call(this, __PATH);
function getMulValue(valueA, valueB, digits = 0, defValue = NaN) {
                                    }
    let product = defValue;
          });


    if ((valueA !== undefined) && (valueB !== undefined)) {
// ==================== Ende Abschnitt fuer Klasse URI ====================
        product = parseFloat(valueA) * parseFloat(valueB);
    }


    return parseFloat(product.toFixed(digits));
// ==================== Abschnitt fuer Klasse Directory ====================
}


// Ueberprueft, ob ein Objekt einer bestimmten Klasse angehoert (ggfs. per Vererbung)
// Basisklasse fuer eine Verzeichnisstruktur
// obj: Ein (generisches) Objekt
// homePath: Absoluter Startpfad als String
// base: Eine Objektklasse (Konstruktor-Funktion)
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// return true, wenn der Prototyp rekursiv gefunden werden konnte
// 'delim': Trennzeichen zwischen zwei Ebenen
function instanceOf(obj, base) {
// 'back': Name des relativen Vaterverzeichnisses
     while (obj !== null) {
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
        if (obj === base.prototype)
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
            return true;
function Directory(homePath, delims) {
        if ((typeof obj) === 'xml') {  // Sonderfall mit Selbstbezug
     'use strict';
            return (base.prototype === XML.prototype);
        }
        obj = Object.getPrototypeOf(obj);
    }


     return false;
     Path.call(this, homePath, delims);
}
}


// Liefert alle Basisklassen des Objekts (inkl. Vererbung)
Class.define(Directory, Path, {
// obj: Ein (generisches) Objekt
                    'chDir' : function(subDir = undefined) {
// return true, wenn der Prototyp rekursiv gefunden werden konnte
                                  if (subDir === undefined) {
function getPrototypes(obj) {
                                      this.root();
    let ret = [];
                                  } else if ((typeof subDir) === 'object') {
                                      for (let sub of subDir) {
                                          this.chDir(sub);
                                      }
                                  } else {
                                      if (subDir === this.delims.home) {
                                          this.home();
                                      } else if (subDir === this.delims.back) {
                                          this.up();
                                      } else {
                                          this.down(subDir);
                                      }
                                  }
                              },
                    'pwd'  : function() {
                                  return this.getPath();
                              }
                });
 
// ==================== Ende Abschnitt fuer Klasse Directory ====================
 
// ==================== Abschnitt fuer Klasse ObjRef ====================


    while (obj !== null) {
// Basisklasse fuer eine Objekt-Referenz
        const __PROTO = Object.getPrototypeOf(obj);
function ObjRef(rootObj) {
    'use strict';


        ret.push(__PROTO);
    Directory.call(this, undefined, new Delims('/', "..", '/', '~'));
        if ((typeof obj) === 'xml') {  // Sonderfall mit Selbstbezug
            break;
        }
        obj = __PROTO;
    }


     return ret;
     this.setConst('rootObj', rootObj); // Wichtig: Verweis nicht verfolgen! Gefahr durch Zyklen!
}
}


// Liefert alle Attribute/Properties des Objekts (inkl. Vererbung)
Class.define(ObjRef, Directory, {
// obj: Ein (generisches) Objekt
                    'valueOf' : function() {
// return Array von Items (Property-Namen)
                                    let ret = this.rootObj;
function getAllProperties(obj) {
    let ret = [];


    for (let o = obj; o !== null; o = Object.getPrototypeOf(o)) {
                                    for (let name of this.dirs) {
      ret = ret.concat(Object.getOwnPropertyNames(o));
                                        if (ret === undefined) {
    }
                                            break;
                                        }
                                        ret = ret[name];
                                    }


    return ret;
                                    return ret;
}
                                }
                });


// Ueberpruefung, ob ein Item aktiv ist oder nicht
// ==================== Ende Abschnitt fuer Klasse ObjRef ====================
// item: Name des betroffenen Items
// inList: Checkliste der inkludierten Items (Positivliste, true fuer aktiv)
// exList: Checkliste der exkludierten Items (Negativliste, true fuer inaktiv)
// return Angabe, ob das Item aktiv ist
function checkItem(item, inList = undefined, exList = undefined) {
    let active = true;


    if (inList !== undefined) {
// ==================== Abschnitt fuer diverse Utilities ====================
        active = (inList[item] === true);  // gesetzt und true
    }
    if (exList !== undefined) {
        if (exList[item] === true) {  // gesetzt und true
            active = false;  // NICHT anzeigen
        }
    }


     return active;
// Gibt einen Wert zurueck. Ist dieser nicht definiert oder null, wird ein Alternativwert geliefert
// value: Ein Wert. Ist dieser nicht undefined oder null, wird er zurueckgeliefert (oder retValue)
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
// retValue: Falls definiert, Rueckgabe-Wert fuer den Fall, dass value nicht undefined oder null ist
// return Der Wert. Sind weder value noch defValue definiert, dann undefined
function getValue(value, defValue = undefined, retValue = undefined) {
     return ((value === undefined) || (value === null)) ? defValue : (retValue === undefined) ? value : retValue;
}
}


// Fuegt Properties zu einem Objekt hinzu, die in einem zweiten stehen. Doppelte Werte werden ueberschrieben
// Gibt einen Wert zurueck. Ist dieser nicht definiert, wird ein Alternativwert geliefert
// data: Objekt, dem Daten hinzugefuegt werden
// value: Ein Wert. Ist dieser definiet und in den Grenzen, wird er zurueckgeliefert
// addData: Objekt, das zusaetzliche Properties enthaelt
// minValue: Untere Grenze fuer den Wert, falls angegeben
// addList: Checkliste der zu setzenden Items (true fuer kopieren), falls angegeben
// minValue: Obere Grenze fuer den Wert, falls angegeben
// ignList: Checkliste der ignorierten Items (true fuer auslassen), falls angegeben
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist oder der Wert ausserhalb liegt
// return Das gemergete Objekt mit allen Properties
// return Der Wert. Sind weder value (in den Grenzen) noch defValue definiert, dann undefined
function addProps(data, addData, addList = undefined, ignList = undefined) {
function getValueIn(value, minValue = undefined, maxValue = undefined, defValue = undefined) {
     for (let item in getValue(addData, { })) {
     const __VALUE = getValue(value, defValue);
        if (checkItem(item, addList, ignList)) {
            data[item] = addData[item];
        }
    }


    return data;
     if ((minValue !== undefined) && (__VALUE < minValue)) {
}
 
// Gibt den Wert einer Property zurueck. Ist dieser nicht definiert oder null, wird er vorher gesetzt
// obj: Ein Objekt. Ist dieses undefined oder null, wird defValue zurueckgeliefert
// item: Key des Properties
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
// return Der Wert des Properties. Sind das obj oder das Property und defValue undefined oder null, dann undefined
function getProp(obj, item, defValue = undefined) {
     if ((obj === undefined) || (obj === null)) {
         return defValue;
         return defValue;
     }
     }
 
     if ((maxValue !== undefined) && (__VALUE > maxValue)) {
    const __PROP = obj[item];
         return defValue;
 
     if ((__PROP !== undefined) && (__PROP !== null)) {
         return __PROP;
     }
     }


     return (obj[item] = defValue);
     return __VALUE;
}
}


// Sicheres obj.valueOf() fuer alle Daten
// Ermittelt den naechsten Wert aus einer Array-Liste
// data: Objekt oder Wert
// arr: Array-Liste mit den moeglichen Werte
// return Bei Objekten valueOf() oder das Objekt selber, bei Werten der Wert
// value: Vorher gesetzter Wert
function valueOf(data) {
// return Naechster Wert in der Array-Liste
     return (((typeof data) === 'object') ? data.valueOf() : data);
function getNextValue(arr, value) {
}
     const __POS = arr.indexOf(value) + 1;


// Sicheres JSON.stringify(), das auch mit Zyklen umgehen kann
    return arr[getValueIn(__POS, 0, arr.length - 1, 0)];
// value: Auszugebene Daten. Siehe JSON.stringify()
// replacer: Elementersetzer. Siehe JSON.stringify()
// space: Verschoenerung. Siehe JSON.stringify()
// cycleReplacer: Ersetzer im Falle von Zyklen
// return String mit Ausgabe der Objektdaten
function safeStringify(value, replacer = undefined, space = undefined, cycleReplacer = undefined) {
    return JSON.stringify(value, serializer(replacer, cycleReplacer), space);
}
}


// Hilfsfunktion fuer safeStringify(): Kapselt replacer und einen cycleReplacer fuer Zyklen
// Gibt ein Produkt zurueck. Ist einer der Multiplikanten nicht definiert, wird ein Alternativwert geliefert
// replacer: Elementersetzer. Siehe JSON.stringify()
// valueA: Ein Multiplikant. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
// cycleReplacer: Ersetzer im Falle von Zyklen
// valueB: Ein Multiplikant. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
// return Ersetzer-Funktion fuer JSON.stringify(), die beide Ersetzer vereint
// digits: Anzahl der Stellen nach dem Komma fuer das Produkt (Default: 0)
function serializer(replacer = undefined, cycleReplacer = undefined) {
// defValue: Default-Wert fuer den Fall, dass ein Multiplikant nicht gesetzt ist (Default: NaN)
     const __STACK = [];
// return Das Produkt auf digits Stellen genau. Ist dieses nicht definiert, dann defValue
    const __KEYS = [];
function getMulValue(valueA, valueB, digits = 0, defValue = NaN) {
     let product = defValue;


     if (! cycleReplacer) {
     if ((valueA !== undefined) && (valueB !== undefined)) {
        cycleReplacer = function(key, value) {
        product = parseFloat(valueA) * parseFloat(valueB);
                if (__STACK[0] === value) {
                    return "[~]";
                }
                return "[~." + __KEYS.slice(0, __STACK.indexOf(value)).join('.') + ']';
            };
     }
     }


     return function(key, value) {
     if (isNaN(product)) {
            if (__STACK.length) {
        product = defValue;
                const __THISPOS = __STACK.indexOf(this);
    }


                if (~ __THISPOS) {
    return parseFloat(product.toFixed(digits));
                    __STACK.splice(__THISPOS + 1);
}
                    __KEYS.splice(__THISPOS, Infinity, key);
                } else {
                    __STACK.push(this);
                    __KEYS.push(key);
                }
                if (~ __STACK.indexOf(value)) {
                    value = cycleReplacer.call(this, key, value);
                }
            } else {
                __STACK.push(value);
            }


            return ((! replacer) ? value : replacer.call(this, key, value));
// Gibt eine Ordinalzahl zur uebergebenen Zahl zurueck
        };
// value: Eine ganze Zahl
// defValue: Default-Wert fuer den Fall, dass der Wert nicht gesetzt ist (Default: '*')
// return Die Ordinalzahl als String der Form "n." oder defValue, falls nicht angegeben
function getOrdinal(value, defValue = '*') {
    return getValue(value, defValue, value + '.');
}
}


// Speichert einen beliebiegen (strukturierten) Wert unter einem Namen ab
// Hilfsfunktion fuer Array.sort(): Vergleich zweier Zahlen
// name: GM_setValue-Name, unter dem die Daten gespeichert werden
// valueA: Erster Zahlenwert
// value: Beliebiger (strukturierter) Wert
// valueB: Zweiter Zahlenwert
// return String-Darstellung des Wertes
// return -1 = kleiner, 0 = gleich, +1 = groesser
function serialize(name, value) {
function compareNumber(valueA, valueB) {
     const __STREAM = (value !== undefined) ? safeStringify(value) : value;
     return +(valueA > valueB) || (+(valueA === valueB) - 1);
 
    __LOG[4](name + " >> " + __STREAM);
 
    GM_setValue(name, __STREAM);
 
    return __STREAM;
}
}


// Holt einen beliebiegen (strukturierter) Wert unter einem Namen zurueck
// Ueberprueft, ob ein Objekt einer bestimmten Klasse angehoert (ggfs. per Vererbung)
// name: GM_setValue-Name, unter dem die Daten gespeichert werden
// obj: Ein (generisches) Objekt
// defValue: Default-Wert fuer den Fall, dass nichts gespeichert ist
// base: Eine Objektklasse (Konstruktor-Funktion)
// return Objekt, das unter dem Namen gespeichert war
// return true, wenn der Prototyp rekursiv gefunden werden konnte
function deserialize(name, defValue = undefined) {
function instanceOf(obj, base) {
     const __STREAM = GM_getValue(name, defValue);
     while (obj !== null) {
 
        if (obj === base.prototype) {
    __LOG[4](name + " << " + __STREAM);
             return true;
 
         }
    if ((__STREAM !== undefined) && __STREAM.length) {
        if ((typeof obj) === 'xml') { // Sonderfall mit Selbstbezug
        try {
             return (base.prototype === XML.prototype);
             return JSON.parse(__STREAM);
         } catch (ex) {
             __LOG[1](name + ": " + ex.message);
         }
         }
        obj = Object.getPrototypeOf(obj);
     }
     }


     return undefined;
     return false;
}
}


// Setzt eine Option dauerhaft und laedt die Seite neu
// Liefert alle Basisklassen des Objekts (inkl. Vererbung)
// name: Name der Option als Speicherort
// obj: Ein (generisches) Objekt
// value: Zu setzender Wert
// return true, wenn der Prototyp rekursiv gefunden werden konnte
// reload: Seite mit neuem Wert neu laden
function getPrototypes(obj) {
// return Gespeicherter Wert fuer setOptValue()
     let ret = [];
function setStored(name, value, reload = false, serial = false) {
 
     if (serial) {
    while (obj !== null) {
        serialize(name, value);
         const __PROTO = Object.getPrototypeOf(obj);
    } else {
         GM_setValue(name, value);
    }


    if (reload) {
        ret.push(__PROTO);
         window.location.reload();
        if ((typeof obj) === 'xml') { // Sonderfall mit Selbstbezug
            break;
        }
         obj = __PROTO;
     }
     }


     return value;
     return ret;
}
}


// Setzt den naechsten Wert aus einer Array-Liste als Option
// Liefert alle Attribute/Properties des Objekts (inkl. Vererbung)
// arr: Array-Liste mit den moeglichen Optionen
// obj: Ein (generisches) Objekt
// name: Name der Option als Speicherort
// return Array von Items (Property-Namen)
// value: Vorher gesetzter Wert
function getAllProperties(obj) {
// reload: Seite mit neuem Wert neu laden
    let ret = [];
// return Gespeicherter Wert fuer setOptValue()
 
function setNextStored(arr, name, value, reload = false, serial = false) {
    for (let o = obj; o !== null; o = Object.getPrototypeOf(o)) {
    return setStored(name, getNextValue(arr, value), reload, serial);
      ret = ret.concat(Object.getOwnPropertyNames(o));
    }
 
    return ret;
}
}


// Fuehrt die in einem Storage gespeicherte Operation aus
// Ueberpruefung, ob ein Item aktiv ist oder nicht
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// item: Name des betroffenen Items
// return Array von Objekten mit 'cmd' / 'key' / 'val' (derzeit maximal ein Kommando) oder undefined
// inList: Checkliste der inkludierten Items (Positivliste, true fuer aktiv)
function getStoredCmds(memory = undefined) {
// exList: Checkliste der exkludierten Items (Negativliste, true fuer inaktiv)
     const __STORAGE = getMemory(memory);
// return Angabe, ob das Item aktiv ist
    const __MEMORY = __STORAGE.Value;
function checkItem(item, inList = undefined, exList = undefined) {
    const __RUNPREFIX = __STORAGE.Prefix;
     let active = true;
    const __STOREDCMDS = [];


     if (__MEMORY !== undefined) {
     if (inList) {
         const __GETITEM = function(item) {
         active = (inList[item] === true); // gesetzt und true
                              return __MEMORY.getItem(__RUNPREFIX + item);
    }
                          };
    if (exList) {
        const __DELITEM = function(item) {
        if (exList[item] === true) { // gesetzt und true
                              return __MEMORY.removeItem(__RUNPREFIX + item);
             active = false; // NICHT anzeigen
                          };
        const __CMD = ((__MEMORY !== undefined) ? __GETITEM('cmd') : undefined);
 
        if (__CMD !== undefined) {
             const __KEY = __GETITEM('key');
            let value = __GETITEM('val');
 
            try {
                value = JSON.parse(value);
            } catch (ex) {
                __LOG[1]("getStoredCmds(): " + __CMD + " '" + __KEY + "' hat illegalen Wert '" + value + "'");
                // ... meist kann man den String selber aber speichern, daher kein "return"...
            }
 
            __STOREDCMDS.push({
                                'cmd' : __CMD,
                                'key' : __KEY,
                                'val' : value
                            });
         }
         }
        __DELITEM('cmd');
        __DELITEM('key');
        __DELITEM('val');
     }
     }


     return (__STOREDCMDS.length ? __STOREDCMDS : undefined);
     return active;
}
 
// Fuegt Properties zu einem Objekt hinzu, die in einem zweiten stehen. Doppelte Werte werden ueberschrieben
// data: Objekt, dem Daten hinzugefuegt werden
// addData: Objekt, das zusaetzliche Properties enthaelt
// addList: Checkliste der zu setzenden Items (true fuer kopieren), falls angegeben
// ignList: Checkliste der ignorierten Items (true fuer auslassen), falls angegeben
// return Das gemergete Objekt mit allen Properties
function addProps(data, addData, addList = undefined, ignList = undefined) {
    for (let item in getValue(addData, { })) {
        if (checkItem(item, addList, ignList)) {
            data[item] = addData[item];
        }
    }
 
    return data;
}
}


// Fuehrt die in einem Storage gespeicherte Operation aus
// Entfernt Properties in einem Objekt
// storedCmds: Array von Objekten mit 'cmd' / 'key' / 'val' (siehe getStoredCmds())
// data: Objekt, deren Properties bearbeitet werden
// optSet: Set mit den Optionen
// delList: Checkliste der zu loeschenden Items (true fuer loeschen), falls angegeben
// beforeLoad: Angabe, ob nach der Speicherung noch loadOptions() aufgerufen wird
// ignList: Checkliste der ignorierten Items (true fuer auslassen), falls angegeben
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return Das veraenderte Objekt ohne die geloeschten Properties
// return Array von Operationen (wie storedCmds), die fuer die naechste Phase uebrig bleiben
function delProps(data, delList = undefined, ignList = undefined) {
function runStoredCmds(storedCmds, optSet = undefined, beforeLoad = undefined) {
     for (let item in getValue(data, { })) {
     const __BEFORELOAD = getValue(beforeLoad, true);
        if (checkItem(item, delList, ignList)) {
    const __STOREDCMDS = getValue(storedCmds, []);
            delete data[item];
    const __LOADEDCMDS = [];
        }
     let invalidated = false;
     }


     while (__STOREDCMDS.length) {
     return data;
        const __STORED = __STOREDCMDS.shift();
}
        const __CMD = __STORED.cmd;
        const __KEY = __STORED.key;
        const __VAL = __STORED.val;


        if (__BEFORELOAD) {
// Gibt den Wert einer Property zurueck. Ist dieser nicht definiert oder null, wird er vorher gesetzt
            if (__STOREDCMDS.length) {
// obj: Ein Objekt. Ist dieses undefined oder null, wird defValue zurueckgeliefert
                invalidateOpts(optSet);  // alle Optionen invalidieren
// item: Key des Properties
                invalidated = true;
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
            }
// return Der Wert des Properties. Sind das obj oder das Property und defValue undefined oder null, dann undefined
            switch (__OPTACTION[__CMD]) {
function getProp(obj, item, defValue = undefined) {
            case __OPTACTION.SET : __LOG[4]("SET '" + __KEY + "' " + __VAL);
    if ((obj === undefined) || (obj === null)) {
                                  setStored(__KEY, __VAL, false, false);
        return defValue;
                                  break;
            case __OPTACTION.NXT : __LOG[4]("SETNEXT '" + __KEY + "' " + __VAL);
                                  //setNextStored(__CONFIG.Choice, __KEY, __VAL, false, false);
                                  setStored(__KEY, __VAL, false, false);
                                  break;
            case __OPTACTION.RST : __LOG[4]("RESET (delayed)");
                                  __LOADEDCMDS.push(__STORED);
                                  break;
            default :              break;
            }
        } else {
            switch (__OPTACTION[__CMD]) {
            case __OPTACTION.SET :
            case __OPTACTION.NXT : __LOG[2]("SET/SETNEXT (undefined)");
                                  break;
            case __OPTACTION.RST : __LOG[4]("RESET");
                                  resetOptions(optSet, false);
                                  loadOptions(optSet); // Reset auf umbenannte Optionen anwenden!
                                  break;
            default :              break;
            }
        }
     }
     }


     return (__LOADEDCMDS.length ? __LOADEDCMDS : undefined);
     const __PROP = obj[item];
}
 
    if ((__PROP !== undefined) && (__PROP !== null)) {
        return __PROP;
    }


// Gibt eine Option sicher zurueck
    return (obj[item] = defValue);
// opt: Config und Value der Option, ggfs. undefined
// defOpt: Rueckgabewert, falls undefined
// return Daten zur Option (oder defOpt)
function getOpt(opt, defOpt = { }) {
    return getValue(opt, defOpt);
}
}


// Gibt eine Option sicher zurueck (Version mit Key)
// Sicheres obj.valueOf() fuer alle Daten
// optSet: Platz fuer die gesetzten Optionen (und Config)
// data: Objekt oder Wert
// item: Key der Option
// return Bei Objekten valueOf() oder das Objekt selber, bei Werten der Wert
// defOpt: Rueckgabewert, falls nicht zu finden
function valueOf(data) {
// return Daten zur Option (oder defOpt)
     return (((typeof data) === 'object') ? data.valueOf() : data);
function getOptByName(optSet, item, defOpt = { }) {
     if ((optSet !== undefined) && (item !== undefined)) {
        return getOpt(optSet[item], defOpt);
    } else {
        return defOpt;
    }
}
}


// Gibt die Konfigurationsdaten einer Option zurueck
// Sicheres JSON.stringify(), das auch mit Zyklen umgehen kann
// opt: Config und Value der Option
// value: Auszugebene Daten. Siehe JSON.stringify()
// defConfig: Rueckgabewert, falls Config nicht zu finden
// replacer: Elementersetzer. Siehe JSON.stringify()
// return Konfigurationsdaten der Option
// space: Verschoenerung. Siehe JSON.stringify()
function getOptConfig(opt, defConfig = { }) {
// cycleReplacer: Ersetzer im Falle von Zyklen
     return getValue(getOpt(opt).Config, defConfig);
// return String mit Ausgabe der Objektdaten
function safeStringify(value, replacer = undefined, space = undefined, cycleReplacer = undefined) {
     return JSON.stringify(value, serializer(replacer, cycleReplacer), space);
}
}


// Setzt den Namen einer Option
// Hilfsfunktion fuer safeStringify(): Kapselt replacer und einen cycleReplacer fuer Zyklen
// opt: Config und Value der Option
// replacer: Elementersetzer. Siehe JSON.stringify()
// name: Zu setzender Name der Option
// cycleReplacer: Ersetzer im Falle von Zyklen
// reload: Seite mit neuem Wert neu laden
// return Ersetzer-Funktion fuer JSON.stringify(), die beide Ersetzer vereint
// return Gesetzter Name der Option
function serializer(replacer = undefined, cycleReplacer = undefined) {
function setOptName(opt, name) {
     const __STACK = [];
     const __CONFIG = getOptConfig(opt);
     const __KEYS = [];
     const __NAME = getOptName(opt);


     if (__NAME !== name) {
     if (! cycleReplacer) {
        __LOG[4]("RENAME " + __NAME + " => " + name);
        cycleReplacer = function(key, value) {
                if (__STACK[0] === value) {
                    return "[~]";
                }
                return "[~." + __KEYS.slice(0, __STACK.indexOf(value)).join('.') + ']';
            };
    }
 
    return function(key, value) {
            if (__STACK.length) {
                const __THISPOS = __STACK.indexOf(this);


        __CONFIG.Name = name;
                if (~ __THISPOS) {
    }
                    __STACK.splice(__THISPOS + 1);
                    __KEYS.splice(__THISPOS, Infinity, key);
                } else {
                    __STACK.push(this);
                    __KEYS.push(key);
                }
                if (~ __STACK.indexOf(value)) {
                    value = cycleReplacer.call(this, key, value);
                }
            } else {
                __STACK.push(value);
            }


    return name;
            return ((! replacer) ? value : replacer.call(this, key, value));
        };
}
}


// Gibt den Namen einer Option zurueck
// Replacer fuer JSON.stringify() oder safeStringify(), der Arrays kompakter darstellt
// opt: Config und Value der Option
// key: Der uebergebene Schluessel
// return Name der Option
// value: Der uebergebene Wert
function getOptName(opt) {
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
     const __CONFIG = getOptConfig(opt);
function replaceArraySimple(key, value) {
     const __NAME = __CONFIG.Name;
     if (Array.isArray(value)) {
        return "[ " + value.join(", ") + " ]";
     }


     if (! __NAME) {
     return value;
        const __SHARED = __CONFIG.Shared;
}


        if (__SHARED && ! opt.Loaded) {
// Replacer fuer JSON.stringify() oder safeStringify(), der Arrays kompakter darstellt
            const __OBJREF = getSharedRef(__SHARED, opt.Item);
// key: Der uebergebene Schluessel
 
// value: Der uebergebene Wert
            return __OBJREF.getPath();
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
        }
function replaceArray(key, value) {
    if (Array.isArray(value)) {
        __RET = value.map(function(element) {
                              return safeStringify(element, replaceArray, 0);
                          });


         showAlert("Error", "Option ohne Namen", safeStringify(__CONFIG));
         return __RET;
     }
     }


     return __NAME;
     return value;
}
}


// Setzt den Wert einer Option
// Moegliche einfache Ersetzungen mit '$'...
// opt: Config und Value der Option
let textSubst;
// name: Zu setzender Wert der Option
 
// return Gesetzter Wert
// Substituiert '$'-Parameter in einem Text
function setOptValue(opt, value) {
// text: Urspruenglicher Text mit '$'-Befehlen
     if (opt !== undefined) {
// par1: Der (erste) uebergebene Parameter
        if (! opt.ReadOnly) {
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
            __LOG[6](getOptName(opt) + ": " + __LOG.changed(opt.Value, value));
function substParam(text, par1) {
     let ret = getValue(text, "");


            opt.Value = value;
    if (! textSubst) {
        }
        textSubst  = {
        return opt.Value;
                'n' : __DBMOD.name,
    } else {
                'v' : __DBMOD.version,
        return undefined;
                'V' : __DBMOD.Name
            };
     }
     }
}


// Gibt den Wert einer Option zurueck
    for (let ch in textSubst) {
// opt: Config und Value der Option
        const __SUBST = textSubst[ch];
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
// load: Laedt die Option per loadOption(), falls noetig
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Gesetzter Wert
function getOptValue(opt, defValue = undefined, load = true, force = false) {
    let value;


    if (opt !== undefined) {
        ret = ret.replace('$' + ch, __SUBST);
        if (load && ! opt.Loaded) {
            value = loadOption(opt, force);
        } else {
            value = opt.Value;
        }
     }
     }


     return valueOf(getValue(value, defValue));
     return ret.replace('$', par1);
}
}


// ==================== Ende Abschnitt fuer diverse Utilities ====================
// Fuegt in die uebergebene Zahl Tausender-Trennpunkte ein
// Wandelt einen etwaig vorhandenen Dezimalpunkt in ein Komma um
// numberString: Dezimalzahl als String
// return Diese Dezimalzahl als String mit Tausender-Trennpunkten und Komma statt Dezimalpunkt
function getNumberString(numberString) {
    if (numberString.lastIndexOf('.') !== -1) {
        // Zahl enthaelt Dezimalpunkt
        const __VORKOMMA = numberString.substring(0, numberString.lastIndexOf('.'));
        const __NACHKOMMA = numberString.substring(numberString.lastIndexOf('.') + 1, numberString.length);


// ==================== Abschnitt fuer Speicher und die Scriptdatenbank ====================
        return getNumberString(__VORKOMMA) + ',' + __NACHKOMMA;
    } else {
        // Kein Dezimalpunkt, fuege Tausender-Trennpunkte ein:
        // String umdrehen, nach jedem dritten Zeichen Punkt einfuegen, dann wieder umdrehen:
        const __TEMP = reverseString(numberString);
        let result = "";


// Namen des Default-, Temporaer- und Null-Memories...
        for (let i = 0; i < __TEMP.length; i++) {
const __MEMNORMAL  = 'normal';
            if ((i > 0) && (i % 3 === 0)) {
const __MEMSESSION  = 'begrenzt';
                result += '.';
const __MEMINAKTIVE = 'inaktiv';
            }
            result += __TEMP.substr(i, 1);
        }


// Definition des Default-, Dauer- und Null-Memories...
        return reverseString(result);
const __OPTMEMNORMAL  = __OPTMEM[__MEMNORMAL];
    }
const __OPTMEMSESSION  = __OPTMEM[__MEMSESSION];
}
const __OPTMEMINAKTIVE = __OPTMEM[__MEMINAKTIVE];


// Medium fuer die Datenbank (Speicher)
// Dreht den uebergebenen String um
let myOptMem = __OPTMEMNORMAL;
// string: Eine Zeichenkette
let myOptMemSize;
// return Dieselbe Zeichenkette rueckwaerts
function reverseString(string) {
    let result = "";


// Infos ueber dieses Script-Modul
    for (let i = string.length - 1; i >= 0; i--) {
const __DBMOD = new ScriptModule();
        result += string.substr(i, 1);
    }


// Inhaltsverzeichnis der DB-Daten (indiziert durch die Script-Namen)
    return result;
const __DBTOC = { };
}


// Daten zu den Modulen (indiziert durch die Script-Namen)
// Speichert einen String/Integer/Boolean-Wert unter einem Namen ab
const __DBDATA = { };
// name: GM.setValue()-Name, unter dem die Daten gespeichert werden
// value: Zu speichernder String/Integer/Boolean-Wert
// return Promise auf ein Objekt, das 'name' und 'value' der Operation enthaelt
function storeValue(name, value) {
    __LOG[4](name + " >> " + value);


// ==================== Abschnitt fuer Speicher ====================
    return GM.setValue(name, value).then(voidValue => {
            __LOG[5]("OK " + name + " >> " + value);


// Ermittelt fuer die uebergebene Speicher-Konfiguration einen Speicher
            return Promise.resolve({
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
                    'name'  : name,
// defMemory: Ersatz-Wert, falls memory undefined. Soll nur memory genutzt werden, dann z.B. null uebergeben!
                    'value' : value
// return memory, falls okay, sonst einen Defaultwert
                });
function getMemory(memory = undefined, defMemory = getValue(myOptMem, __OPTMEMNORMAL)) {
        }, defaultCatch);
    return getValue(memory, defMemory);
}
}


// Kompatibilitaetsfunktion: Testet, ob der uebergebene Speicher genutzt werden kann
// Holt einen String/Integer/Boolean-Wert unter einem Namen zurueck
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// name: GM.getValue()-Name, unter dem die Daten gespeichert wurden
// return true, wenn der Speichertest erfolgreich war
// defValue: Default-Wert fuer den Fall, dass nichts gespeichert ist
function canUseMemory(memory = undefined) {
// return Promise fuer den String/Integer/Boolean-Wert, der unter dem Namen gespeichert war
     const __STORAGE = getMemory(memory, { });
function summonValue(name, defValue = undefined) {
    const __MEMORY = __STORAGE.Value;
     return GM.getValue(name, defValue).then(value => {
    let ret = false;
            __LOG[4](name + " << " + value);


    if (__MEMORY !== undefined) {
            return Promise.resolve(value);
         const __TESTPREFIX = 'canUseStorageTest';
         }, ex => {
        const __TESTDATA = Math.random().toString();
            __LOG[0](name + ": " + ex.message);
        const __TESTITEM = __TESTPREFIX + __TESTDATA;


        __MEMORY.setItem(__TESTITEM, __TESTDATA);
            return Promise.reject(ex);
         ret = (__MEMORY.getItem(__TESTITEM) === __TESTDATA);
         }, defaultCatch);
        __MEMORY.removeItem(__TESTITEM);
}
    }


    __LOG[2]("canUseStorage(" + __STORAGE.Name + ") = " + ret);
// Speichert einen beliebiegen (strukturierten) Wert unter einem Namen ab
// name: GM.setValue()-Name, unter dem die Daten gespeichert werden
// value: Beliebiger (strukturierter) Wert
// return Promise auf ein Objekt, das 'name' und 'value' in der String-Darstellung des Wertes enthaelt
function serialize(name, value) {
    const __STREAM = ((value !== undefined) ? safeStringify(value) : value);


     return ret;
     return storeValue(name, __STREAM);
}
}


// Ermittelt die Groesse des benutzten Speichers
// Holt einen beliebiegen (strukturierter) Wert unter einem Namen zurueck
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// name: GM.getValue()-Name, unter dem die Daten gespeichert wurden
// return Groesse des genutzten Speichers in Bytes
// defValue: Default-Wert fuer den Fall, dass nichts gespeichert ist
function getMemSize(memory = undefined) {
// return Promise fuer das Objekt, das unter dem Namen gespeichert war
     const __STORAGE = getMemory(memory);
function deserialize(name, defValue = undefined) {
    const __MEMORY = __STORAGE.Value;
     return summonValue(name).then(stream => {
            if (stream && stream.length) {
                return JSON.parse(stream);
            } else {
                return defValue;
            }
        });
}


    //getMemUsage(__MEMORY);
// Setzt die Seite gemaess der Aenderungen zurueck...
 
// reload: Seite wird ganz neu geladen
    if (__MEMORY !== undefined) {
function refreshPage(reload = true) {
        const __SIZE = safeStringify(__MEMORY).length;
    if (reload) {
 
         __LOG[2]("Seite wird neu geladen...");
         __LOG[2]("MEM: " + __SIZE + " bytes");
         window.location.reload();
         return __SIZE;
    } else {
        return 0;
     }
     }
}
}


// Gibt rekursiv und detailliert die Groesse des benutzten Speichers fuer ein Objekt aus
// Setzt eine Option dauerhaft und laedt die Seite neu
// value: (Enumerierbares) Objekt oder Wert, dessen Groesse gemessen wird
// name: Name der Option als Speicherort
// out: Logfunktion, etwa console.log
// value: Zu setzender Wert
// depth: Gewuenschte Rekursionstiefe (0 = nur dieses Objekt, -1 = alle Ebenen)
// reload: Seite mit neuem Wert neu laden
// name: Name des Objekts
// serial: Serialization fuer komplexe Daten
function getMemUsage(value = undefined, out = undefined, depth = -1, name = '$') {
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
     const __OUT = (out || __LOG[4]);
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gespeicherter Wert fuer setOptValue()
function setStored(name, value, reload = false, serial = false, onFulfilled = undefined, onRejected = undefined) {
     (serial ? serialize(name, value)
            : storeValue(name, value))
                .then(onFulfilled, onRejected)
                .then(() => refreshPage(reload), defaultCatch); // Ende der Kette...


     if ((typeof value) === 'string') {
     return value;
        const __SIZE = value.length;
}


        __OUT("USAGE: " + name + '\t' + __SIZE + '\t' + value.substr(0, 255));
// Setzt den naechsten Wert aus einer Array-Liste als Option
    } else if ((typeof value) === 'object') {
// arr: Array-Liste mit den moeglichen Optionen
        if (depth === 0) {
// name: Name der Option als Speicherort
            const __SIZE = safeStringify(value).length;
// value: Vorher gesetzter Wert
// reload: Seite mit neuem Wert neu laden
// serial: Serialization fuer komplexe Daten
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gespeicherter Wert fuer setOptValue()
function setNextStored(arr, name, value, reload = false, serial = false, onFulfilled = undefined, onRejected = undefined) {
    return setStored(name, getNextValue(arr, value), reload, serial, onFulfilled, onRejected);
}


            __OUT("USAGE: " + name + '\t' + __SIZE);
// Fuehrt die in einem Storage gespeicherte Operation aus
         } else {
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
             depth--;
// return Array von Objekten mit 'cmd' / 'key' / 'val' (derzeit maximal ein Kommando) oder undefined
             for (let sub in value) {
function getStoredCmds(memory = undefined) {
                 getMemUsage(value[sub], __OUT, depth, name + '.' + sub);
    const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;
    const __RUNPREFIX = __STORAGE.Prefix;
    const __STOREDCMDS = [];
 
    if (__MEMORY !== undefined) {
        const __GETITEM = function(item) {
                              return __MEMORY.getItem(__RUNPREFIX + item);
                          };
        const __DELITEM = function(item) {
                              return __MEMORY.removeItem(__RUNPREFIX + item);
                          };
        const __CMD = ((__MEMORY !== undefined) ? __GETITEM('cmd') : undefined);
 
         if (__CMD !== undefined) {
             const __KEY = __GETITEM('key');
             let value = __GETITEM('val');
 
            try {
                 value = JSON.parse(value);
            } catch (ex) {
                __LOG[0]("getStoredCmds(): " + __CMD + " '" + __KEY + "' hat illegalen Wert '" + value + "'");
                // ... meist kann man den String selber aber speichern, daher kein "return"...
             }
             }
             getMemUsage(value, __OUT, 0, name);
 
             __STOREDCMDS.push({
                                'cmd' : __CMD,
                                'key' : __KEY,
                                'val' : value
                            });
         }
         }
    } else {
      const __DATA = (((typeof value) === 'function') ? "" : '\t' + value);


         __OUT("USAGE: " + name + '\t' + (typeof value) + __DATA);
         __DELITEM('cmd');
        __DELITEM('key');
        __DELITEM('val');
     }
     }
}


// Restauriert den vorherigen Speicher (der in einer Option definiert ist)
    return (__STOREDCMDS.length ? __STOREDCMDS : undefined);
// opt: Option zur Wahl des Speichers
// return Gesuchter Speicher oder Null-Speicher ('inaktiv')
function restoreMemoryByOpt(opt) {
    // Memory Storage fuer vorherige Speicherung...
    const __STORAGE = getOptValue(opt, __MEMNORMAL, true, true);
 
    return __OPTMEM[__STORAGE];
}
}


// Initialisiert den Speicher (der in einer Option definiert ist) und merkt sich diesen ggfs.
// Fuehrt die in einem Storage gespeicherte Operation aus
// opt: Option zur Wahl des Speichers
// storedCmds: Array von Objekten mit 'cmd' / 'key' / 'val' (siehe getStoredCmds())
// saveOpt: Option zur Speicherung der Wahl des Speichers (fuer restoreMemoryByOpt)
// optSet: Object mit den Optionen
// return Gesuchter Speicher oder Null-Speicher ('inaktiv'), falls speichern nicht moeglich ist
// beforeLoad: Angabe, ob nach der Speicherung noch loadOptions() aufgerufen wird
function startMemoryByOpt(opt, saveOpt = undefined) {
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
     // Memory Storage fuer naechste Speicherung...
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
     let storage = getOptValue(opt, __MEMNORMAL);
// return Promise auf ein Array von Operationen (wie storedCmds), die fuer die naechste Phase uebrig bleiben
     let optMem = __OPTMEM[storage];
async function runStoredCmds(storedCmds, optSet = undefined, beforeLoad = undefined, onFulfilled = undefined, onRejected = undefined) {
     const __BEFORELOAD = getValue(beforeLoad, true);
     const __STOREDCMDS = getValue(storedCmds, []);
     const __LOADEDCMDS = [];
    let invalidated = false;


     if (! canUseMemory(optMem)) {
     while (__STOREDCMDS.length) {
         if (storage !== __MEMINAKTIVE) {
         const __STORED = __STOREDCMDS.shift();
            storage = __MEMINAKTIVE;
        const __CMD = __STORED.cmd;
            optMem = __OPTMEM[storage];
        const __KEY = __STORED.key;
         }
         const __VAL = __STORED.val;
    }


    if (saveOpt !== undefined) {
        if (__BEFORELOAD) {
        setOpt(saveOpt, storage, false);
            if (__STOREDCMDS.length) {
    }
                await invalidateOpts(optSet);  // alle Optionen invalidieren
 
                invalidated = true;
    return optMem;
            }
}
            switch (__OPTACTION[__CMD]) {
 
            case __OPTACTION.SET : __LOG[4]("SET '" + __KEY + "' " + __VAL);
// ==================== Ende Abschnitt fuer Speicher ====================
                                  setStored(__KEY, __VAL, false, false, onFulfilled, onRejected);
 
                                  break;
// ==================== Abschnitt fuer die Scriptdatenbank ====================
            case __OPTACTION.NXT : __LOG[4]("SETNEXT '" + __KEY + "' " + __VAL);
                                  //setNextStored(__CONFIG.Choice, __KEY, __VAL, false, false, onFulfilled, onRejected);
                                  setStored(__KEY, __VAL, false, false, onFulfilled, onRejected);
                                  break;
            case __OPTACTION.RST : __LOG[4]("RESET (delayed)");
                                  __LOADEDCMDS.push(__STORED);
                                  break;
            default :              break;
            }
        } else {
            switch (__OPTACTION[__CMD]) {
            case __OPTACTION.SET :
            case __OPTACTION.NXT : __LOG[2]("SET/SETNEXT (undefined)");
                                  break;
            case __OPTACTION.RST : __LOG[4]("RESET");
                                  await resetOptions(optSet, false);
                                  await loadOptions(optSet);  // Reset auf umbenannte Optionen anwenden!
                                  break;
            default :              break;
            }
        }
    }


// Initialisiert das Script-Modul und ermittelt die beschreibenden Daten
    return (__LOADEDCMDS.length ? __LOADEDCMDS : undefined);
// meta: Metadaten des Scripts (Default: GM_info.script)
}
// return Beschreibende Daten fuer __DBMOD
function ScriptModule(meta) {
    'use strict';


    const __META = getValue(meta, GM_info.script);
// Gibt eine Option sicher zurueck
    const __PROPS = {
// opt: Config und Value der Option, ggfs. undefined
                'name'        : true,
// defOpt: Rueckgabewert, falls undefined
                'version'     : true,
// return Daten zur Option (oder defOpt)
                'namespace'  : true,
function getOpt(opt, defOpt = { }) {
                'description' : true
     return getValue(opt, defOpt);
            };
}


    const __DBMOD = { };
// Gibt eine Option sicher zurueck (Version mit Key)
 
// optSet: Platz fuer die gesetzten Optionen (und Config)
    __LOG[5](__META);
// item: Key der Option
 
// defOpt: Rueckgabewert, falls nicht zu finden
    // Infos zu diesem Script...
// return Daten zur Option (oder defOpt)
     addProps(__DBMOD, __META, __PROPS);
function getOptByName(optSet, item, defOpt = { }) {
     if ((optSet !== undefined) && (item !== undefined)) {
        return getOpt(optSet[item], defOpt);
    } else {
        return defOpt;
    }
}


    // Voller Name fuer die Ausgabe...
// Gibt die Konfigurationsdaten einer Option zurueck
    Object.defineProperty(__DBMOD, 'Name', {
// opt: Config und Value der Option
                    get : function() {
// defConfig: Rueckgabewert, falls Config nicht zu finden
                              return this.name + " (" + this.version + ')';
// return Konfigurationsdaten der Option
                          },
function getOptConfig(opt, defConfig = { }) {
                    set : undefined
    return getValue(getOpt(opt).Config, defConfig);
                });
}


     __LOG[2](__DBMOD);
// Setzt den Namen einer Option
// opt: Config und Value der Option
// name: Zu setzender Name der Option
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Name der Option
function setOptName(opt, name) {
    const __CONFIG = getOptConfig(opt);
     const __NAME = getOptName(opt);


     return __DBMOD;
     if (__NAME !== name) {
}
        __LOG[4]("RENAME " + __NAME + " => " + name);


Class.define(ScriptModule, Object);
        __CONFIG.Name = name;
    }


// Initialisiert die Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
    return name;
// optSet: Gesetzte Optionen (und Config)
}
function initScriptDB(optSet) {
 
    // Speicher fuer die DB-Daten...
// Gibt den Namen einer Option zurueck
     const __DBMEM = myOptMem.Value;
// opt: Config und Value der Option
// return Name der Option
function getOptName(opt) {
    const __CONFIG = getOptConfig(opt);
     const __NAME = __CONFIG.Name;


     __DBTOC.versions = getValue((__DBMEM === undefined) ? undefined : JSON.parse(__DBMEM.getItem('__DBTOC.versions')), { });
     if (! __NAME) {
    __DBTOC.namespaces = getValue((__DBMEM === undefined) ? undefined : JSON.parse(__DBMEM.getItem('__DBTOC.namespaces')), { });
        const __SHARED = __CONFIG.Shared;


    // Zunaechst den alten Eintrag entfernen...
        if (__SHARED && ! opt.Loaded) {
    delete __DBTOC.versions[__DBMOD.name];
            const __OBJREF = getSharedRef(__SHARED, opt.Item);
    delete __DBTOC.namespaces[__DBMOD.name];


    if (__DBMEM !== undefined) {
             return __OBJREF.getPath();
        // ... und die Daten der Fremdscripte laden...
        for (let module in __DBTOC.versions) {
             scriptDB(module, getValue(JSON.parse(__DBMEM.getItem('__DBDATA.' + module)), { }));
         }
         }
        showAlert("Error", "Option ohne Namen", safeStringify(__CONFIG));
     }
     }
    return __NAME;
}
}


// Setzt die Daten dieses Scriptes in der Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
// Setzt den Wert einer Option
// optSet: Gesetzte Optionen (und Config)
// opt: Config und Value der Option
function updateScriptDB(optSet) {
// name: Zu setzender Wert der Option
     // Eintrag ins Inhaltsverzeichnis...
// return Gesetzter Wert
    __DBTOC.versions[__DBMOD.name] = __DBMOD.version;
function setOptValue(opt, value) {
    __DBTOC.namespaces[__DBMOD.name] = __DBMOD.namespace;
     if (opt !== undefined) {
        if (! opt.ReadOnly) {
            __LOG[6](getOptName(opt) + ": " + __LOG.changed(opt.Value, value));


    // Speicher fuer die DB-Daten...
            opt.Value = value;
     const __DBMEM = myOptMem.Value;
        }
        return opt.Value;
     } else {
        return undefined;
    }
}


    if (__DBMEM !== undefined) {
// Gibt den Wert einer Option zurueck
        // Permanente Speicherung der Eintraege...
// opt: Config und Value der Option
        __DBMEM.setItem('__DBTOC.versions', safeStringify(__DBTOC.versions));
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
        __DBMEM.setItem('__DBTOC.namespaces', safeStringify(__DBTOC.namespaces));
// load: Laedt die Option per loadOption(), falls noetig
        __DBMEM.setItem('__DBDATA.' + __DBMOD.name, safeStringify(optSet));
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Gesetzter Wert
function getOptValue(opt, defValue = undefined, load = true, asyncLoad = false, force = false) {
    let value;


         // Aktualisierung der Speichergroesse...
    if (opt !== undefined) {
         myOptMemSize = getMemSize(myOptMem);
         if (load && ! opt.Loaded) {
            if (! opt.Promise) {
                loadOption(opt, force);
            }
            if (! asyncLoad) {
                __LOG[0]("Warnung: getOptValue(" + getOptName(opt) + ") fordert zum Nachladen auf, daher nur Default-Wert!");
            }
         } else {
            value = opt.Value;
        }
     }
     }


     // Jetzt die inzwischen gefuellten Daten *dieses* Scripts ergaenzen...
     return valueOf(getValue(value, defValue));
    scriptDB(__DBMOD.name, getValue(optSet, { }));
 
    __LOG[2](__DBDATA);
}
}


// Holt die globalen Daten zu einem Modul aus der Scriptdatenbank
// ==================== Ende Abschnitt fuer diverse Utilities ====================
// module: Gesetzte Optionen (und Config)
// initValue: Falls angegeben, zugewiesener Startwert
// return Daten zu diesem Modul
function scriptDB(module, initValue = undefined) {
    const __NAMESPACE = __DBTOC.namespaces[module];
    const __DBMODS = getProp(__DBDATA, __NAMESPACE, { });


    if (initValue !== undefined) {
// ==================== Abschnitt fuer Speicher und die Scriptdatenbank ====================
        return (__DBMODS[module] = initValue);
    } else {
        return getProp(__DBMODS, module, { });
    }
}


// ==================== Ende Abschnitt fuer die Scriptdatenbank ====================
// Namen des Default-, Temporaer- und Null-Memories...
const __MEMNORMAL  = 'normal';
const __MEMSESSION  = 'begrenzt';
const __MEMINAKTIVE = 'inaktiv';


// ==================== Ende Abschnitt fuer Speicher und die Scriptdatenbank ====================
// Definition des Default-, Dauer- und Null-Memories...
const __OPTMEMNORMAL  = __OPTMEM[__MEMNORMAL];
const __OPTMEMSESSION  = __OPTMEM[__MEMSESSION];
const __OPTMEMINAKTIVE = __OPTMEM[__MEMINAKTIVE];


// ==================== Abschnitt fuer das Benutzermenu ====================
// Medium fuer die Datenbank (Speicher)
let myOptMem = __OPTMEMNORMAL;
let myOptMemSize;


// Zeigt den Eintrag im Menu einer Option
// Infos ueber dieses Script-Modul
// val: Derzeitiger Wert der Option
const __DBMOD = new ScriptModule();
// menuOn: Text zum Setzen im Menu
// funOn: Funktion zum Setzen
// keyOn: Hotkey zum Setzen im Menu
// menuOff: Text zum Ausschalten im Menu
// funOff: Funktion zum Ausschalten
// keyOff: Hotkey zum Ausschalten im Menu
function registerMenuOption(val, menuOn, funOn, keyOn, menuOff, funOff, keyOff) {
    const __ON  = (val ? '*' : "");
    const __OFF = (val ? "" : '*');


    __LOG[3]("OPTION " + __ON + menuOn + __ON + " / " + __OFF + menuOff + __OFF);
// Inhaltsverzeichnis der DB-Daten (indiziert durch die Script-Namen)
const __DBTOC = { };


    if (val) {
// Daten zu den Modulen (indiziert durch die Script-Namen)
        GM_registerMenuCommand(menuOff, funOff, keyOff);
const __DBDATA = { };
    } else {
        GM_registerMenuCommand(menuOn, funOn, keyOn);
    }
}


// Zeigt den Eintrag im Menu einer Option mit Wahl des naechsten Wertes
// ==================== Abschnitt fuer Speicher ====================
// val: Derzeitiger Wert der Option
 
// arr: Array-Liste mit den moeglichen Optionen
// Ermittelt fuer die uebergebene Speicher-Konfiguration einen Speicher
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// fun: Funktion zum Setzen des naechsten Wertes
// defMemory: Ersatz-Wert, falls memory undefined. Soll nur memory genutzt werden, dann z.B. null uebergeben!
// key: Hotkey zum Setzen des naechsten Wertes im Menu
// return memory, falls okay, sonst einen Defaultwert
function registerNextMenuOption(val, arr, menu, fun, key) {
function getMemory(memory = undefined, defMemory = getValue(myOptMem, __OPTMEMNORMAL)) {
     const __MENU = menu.replace('$', val);
    return getValue(memory, defMemory);
    let options = "OPTION " + __MENU;
}
 
// Kompatibilitaetsfunktion: Testet, ob der uebergebene Speicher genutzt werden kann
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return true, wenn der Speichertest erfolgreich war
function canUseMemory(memory = undefined) {
    const __STORAGE = getMemory(memory, { });
     const __MEMORY = __STORAGE.Value;
    let ret = false;
 
    if (__MEMORY !== undefined) {
        const __TESTPREFIX = 'canUseStorageTest';
        const __TESTDATA = Math.random().toString();
        const __TESTITEM = __TESTPREFIX + __TESTDATA;


    for (let value of arr) {
        __MEMORY.setItem(__TESTITEM, __TESTDATA);
         if (value === val) {
         ret = (__MEMORY.getItem(__TESTITEM) === __TESTDATA);
            options += " / *" + value + '*';
         __MEMORY.removeItem(__TESTITEM);
         } else {
            options += " / " + value;
        }
     }
     }
    __LOG[3](options);


     GM_registerMenuCommand(__MENU, fun, key);
     __LOG[2]("canUseStorage(" + __STORAGE.Name + ") = " + ret);
 
    return ret;
}
}


// Zeigt den Eintrag im Menu einer Option, falls nicht hidden
// Ermittelt die Groesse des benutzten Speichers
// val: Derzeitiger Wert der Option
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
// return Groesse des genutzten Speichers in Bytes
// fun: Funktion zum Setzen des naechsten Wertes
function getMemSize(memory = undefined) {
// key: Hotkey zum Setzen des naechsten Wertes im Menu
     const __STORAGE = getMemory(memory);
// hidden: Angabe, ob Menupunkt nicht sichtbar sein soll (Default: sichtbar)
     const __MEMORY = __STORAGE.Value;
// serial: Serialization fuer komplexe Daten
 
function registerDataOption(val, menu, fun, key, hidden = false, serial = true) {
     //getMemUsage(__MEMORY);
     const __VALUE = ((serial && (val !== undefined)) ? safeStringify(val) : val);
     const __MENU = getValue(menu, "").replace('$', __VALUE);
     const __OPTIONS = (hidden ? "HIDDEN " : "") + "OPTION " + __MENU +
                      getValue(__VALUE, "", " = " + __VALUE);


     __LOG[hidden ? 4 : 3](__OPTIONS);
     if (__MEMORY !== undefined) {
        const __SIZE = safeStringify(__MEMORY).length;


    if (! hidden) {
        __LOG[2]("MEM: " + __SIZE + " bytes");
         GM_registerMenuCommand(__MENU, fun, key);
        return __SIZE;
    } else {
         return 0;
     }
     }
}
}


// Zeigt den Eintrag im Menu einer Option
// Gibt rekursiv und detailliert die Groesse des benutzten Speichers fuer ein Objekt aus
// opt: Config und Value der Option
// value: (Enumerierbares) Objekt oder Wert, dessen Groesse gemessen wird
function registerOption(opt) {
// out: Logfunktion, etwa __LOG[4]
    const __CONFIG = getOptConfig(opt);
// depth: Gewuenschte Rekursionstiefe (0 = nur dieses Objekt, -1 = alle Ebenen)
    const __VALUE = getOptValue(opt);
// name: Name des Objekts
    const __LABEL = __CONFIG.Label;
function getMemUsage(value = undefined, out = undefined, depth = -1, name = '$') {
    const __ACTION = opt.Action;
     const __OUT = (out || __LOG[4]);
    const __HOTKEY = __CONFIG.Hotkey;
    const __HIDDEN = __CONFIG.HiddenMenu;
     const __SERIAL = __CONFIG.Serial;


     if (! __CONFIG.HiddenMenu) {
     if ((typeof value) === 'string') {
         switch (__CONFIG.Type) {
         const __SIZE = value.length;
         case __OPTTYPES.MC : registerNextMenuOption(__VALUE, __CONFIG.Choice, __LABEL, __ACTION, __HOTKEY);
 
                            break;
         __OUT("USAGE: " + name + '\t' + __SIZE + '\t' + value.slice(0, 255));
         case __OPTTYPES.SW : registerMenuOption(__VALUE, __LABEL, __ACTION, __HOTKEY,
    } else if ((typeof value) === 'object') {
                                                __CONFIG.AltLabel, __ACTION, __CONFIG.AltHotkey);
         if (depth === 0) {
                            break;
            const __SIZE = safeStringify(value).length;
         case __OPTTYPES.TF : registerMenuOption(__VALUE, __LABEL, __ACTION, __HOTKEY,
 
                                                __CONFIG.AltLabel, opt.AltAction, __CONFIG.AltHotkey);
            __OUT("USAGE: " + name + '\t' + __SIZE);
                            break;
         } else {
        case __OPTTYPES.SD : registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
            depth--;
                            break;
            for (let sub in value) {
        case __OPTTYPES.SI : registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
                getMemUsage(value[sub], __OUT, depth, name + '.' + sub);
                            break;
            }
        default :            break;
            getMemUsage(value, __OUT, 0, name);
         }
         }
     } else {
     } else {
        // Nur Anzeige im Log...
      const __DATA = (((typeof value) === 'function') ? "" : '\t' + value);
         registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
 
         __OUT("USAGE: " + name + '\t' + (typeof value) + __DATA);
     }
     }
}
}


// ==================== Ende Abschnitt fuer das Benutzermenu ====================
// Restauriert den vorherigen Speicher (der in einer Option definiert ist)
// opt: Option zur Wahl des Speichers
// return Promise auf gesuchten Speicher oder Null-Speicher ('inaktiv')
async function restoreMemoryByOpt(opt) {
    // Memory Storage fuer vorherige Speicherung...
    const __STORAGE = await getOptValue(opt, __MEMNORMAL, true, true, true);


// Initialisiert die gesetzten Option
    return __OPTMEM[__STORAGE];
// config: Konfiguration der Option
}
// setValue: Zu uebernehmender Default-Wert (z.B. der jetzt gesetzte)
// return Initialwert der gesetzten Option
function initOptValue(config, setValue = undefined) {
    let value = getValue(setValue, config.Default);  // Standard


    if (config.SharedData !== undefined) {
// Initialisiert den Speicher (der in einer Option definiert ist) und merkt sich diesen ggfs.
        value = config.SharedData;
// opt: Option zur Wahl des Speichers
     }
// saveOpt: Option zur Speicherung der Wahl des Speichers (fuer restoreMemoryByOpt)
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesuchter Speicher oder Null-Speicher ('inaktiv'), falls speichern nicht moeglich ist
function startMemoryByOpt(opt, saveOpt = undefined, onFulfilled = undefined, onRejected = undefined) {
    // Memory Storage fuer naechste Speicherung...
    let storage = getOptValue(opt, __MEMNORMAL);
     let optMem = __OPTMEM[storage];


     switch (config.Type) {
     if (! canUseMemory(optMem)) {
    case __OPTTYPES.MC : if ((value === undefined) && (config.Choice !== undefined)) {
        if (storage !== __MEMINAKTIVE) {
                            value = config.Choice[0];
            storage = __MEMINAKTIVE;
                        }
            optMem = __OPTMEM[storage];
                        break;
        }
    case __OPTTYPES.SW : break;
    case __OPTTYPES.TF : break;
    case __OPTTYPES.SD : config.Serial = true;
                        break;
    case __OPTTYPES.SI : break;
    default :            break;
     }
     }


     if (config.Serial || config.Hidden) {
     if (saveOpt !== undefined) {
         config.HiddenMenu = true;
         setOpt(saveOpt, storage, false, onFulfilled, onRejected);
     }
     }


     return value;
     return optMem;
}
}


// Initialisiert die Menue-Funktion einer Option
// ==================== Ende Abschnitt fuer Speicher ====================
// optAction: Typ der Funktion
// item: Key der Option
// optSet: Platz fuer die gesetzten Optionen (und Config)
// optConfig: Konfiguration der Option
// return Funktion fuer die Option
function initOptAction(optAction, item = undefined, optSet = undefined, optConfig = undefined) {
    let fun;


    if (optAction !== undefined) {
// ==================== Abschnitt fuer die Scriptdatenbank ====================
        const __CONFIG = ((optConfig !== undefined) ? optConfig : getOptConfig(getOptByName(optSet, item)));
        const __RELOAD = getValue(getValue(__CONFIG, { }).ActionReload, true);


        switch (optAction) {
// Initialisiert das Script-Modul und ermittelt die beschreibenden Daten
        case __OPTACTION.SET : fun = function() {
// meta: Metadaten des Scripts (Default: GM.info.script)
                                      return setOptByName(optSet, item, __CONFIG.SetValue, __RELOAD);
// return Beschreibende Daten fuer __DBMOD
                                  };
function ScriptModule(meta) {
                              break;
    'use strict';
        case __OPTACTION.NXT : fun = function() {
                                      return promptNextOptByName(optSet, item, __CONFIG.SetValue, __RELOAD,
                                                  __CONFIG.FreeValue, __CONFIG.SelValue, __CONFIG.MinChoice);
                                  };
                              break;
        case __OPTACTION.RST : fun = function() {
                                      return resetOptions(optSet, __RELOAD);
                                  };
                              break;
        default :              break;
        }
    }


     return fun;
     const __META = getValue(meta, GM.info.script);
}
    const __PROPS = {
                'name'        : true,
                'version'    : true,
                'namespace'  : true,
                'description' : true
            };


// Gibt fuer einen 'Shared'-Eintrag eine ObjRef zurueck
     const __DBMOD = { };
// shared: Object mit den Angaben 'namespace', 'module' und ggfs. 'item'
// item: Key der Option
// return ObjRef, die das Ziel definiert
function getSharedRef(shared, item = undefined) {
     if (shared === undefined) {
        return undefined;
    }


     const __OBJREF = new ObjRef(__DBDATA);  // Gemeinsame Daten
     __LOG[5](__META);
    const __PROPS = [ 'namespace', 'module', 'item' ];
    const __DEFAULTS = [ __DBMOD.namespace, __DBMOD.name, item ];


     for (let stage in __PROPS) {
     // Infos zu diesem Script...
        const __DEFAULT = __DEFAULTS[stage];
    addProps(__DBMOD, __META, __PROPS);
        const __PROP = __PROPS[stage];
        const __NAME = shared[__PROP];


        if (__NAME === '$') {
    // Voller Name fuer die Ausgabe...
            break;
    Object.defineProperty(__DBMOD, 'Name', {
        }
                    get : function() {
                              return this.name + " (" + this.version + ')';
                          },
                    set : undefined
                });


        __OBJREF.chDir(getValue(__NAME, __DEFAULT));
    __LOG[2](__DBMOD);
    }


     return __OBJREF;
     return __DBMOD;
}
}


// Gibt diese Config oder, falls 'Shared', ein Referenz-Objekt mit gemeinsamen Daten zurueck
Class.define(ScriptModule, Object);
// optConfig: Konfiguration der Option
// item: Key der Option
// return Entweder optConfig oder gemergete Daten auf Basis des in 'Shared' angegebenen Objekts
function getSharedConfig(optConfig, item = undefined) {
    let config = getValue(optConfig, { });
    const __SHARED = config.Shared;


    if (__SHARED !== undefined) {
// Initialisiert die Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
        const __OBJREF = getSharedRef(__SHARED, item)// Gemeinsame Daten
// optSet: Gesetzte Optionen (und Config)
function initScriptDB(optSet) {
    // Speicher fuer die DB-Daten...
    const __DBMEM = myOptMem.Value;


        if (getValue(__SHARED.item, '$') !== '$') { // __REF ist ein Item
    __DBTOC.versions = getValue((__DBMEM === undefined) ? undefined : JSON.parse(__DBMEM.getItem('__DBTOC.versions')), { });
            const __REF = valueOf(__OBJREF);
    __DBTOC.namespaces = getValue((__DBMEM === undefined) ? undefined : JSON.parse(__DBMEM.getItem('__DBTOC.namespaces')), { });


            config = { };  // Neu aufbauen...
    // Zunaechst den alten Eintrag entfernen...
            addProps(config, getOptConfig(__REF));
    delete __DBTOC.versions[__DBMOD.name];
            addProps(config, optConfig);
    delete __DBTOC.namespaces[__DBMOD.name];
            config.setConst('SharedData', getOptValue(__REF));
        } else {  // __REF enthaelt die Daten selbst
            if (! config.Name) {
                config.Name = __OBJREF.getPath();
            }
            config.setConst('SharedData', __OBJREF); // Achtung: Ggfs. zirkulaer!
        }
    }


     return config;
     if (__DBMEM !== undefined) {
        // ... und die Daten der Fremdscripte laden...
        for (let module in __DBTOC.versions) {
            scriptDB(module, getValue(JSON.parse(__DBMEM.getItem('__DBDATA.' + module)), { }));
        }
    }
}
}


// Initialisiert die gesetzten Optionen
// Setzt die Daten dieses Scriptes in der Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
// optConfig: Konfiguration der Optionen
// optSet: Gesetzte Optionen (und Config)
// optSet: Platz fuer die gesetzten Optionen
function updateScriptDB(optSet) {
// preInit: Vorinitialisierung einzelner Optionen mit 'PreInit'-Attribut
    // Eintrag ins Inhaltsverzeichnis...
// return Gefuelltes Objekt mit den gesetzten Optionen
    __DBTOC.versions[__DBMOD.name] = __DBMOD.version;
function initOptions(optConfig, optSet = undefined, preInit = undefined) {
    __DBTOC.namespaces[__DBMOD.name] = __DBMOD.namespace;
    let value;
 
    // Speicher fuer die DB-Daten...
    const __DBMEM = myOptMem.Value;
 
    if (__DBMEM !== undefined) {
        // Permanente Speicherung der Eintraege...
        __DBMEM.setItem('__DBTOC.versions', safeStringify(__DBTOC.versions));
        __DBMEM.setItem('__DBTOC.namespaces', safeStringify(__DBTOC.namespaces));
        __DBMEM.setItem('__DBDATA.' + __DBMOD.name, safeStringify(optSet));


    if (optSet === undefined) {
        // Aktualisierung der Speichergroesse...
         optSet = { };
         myOptMemSize = getMemSize(myOptMem);
     }
     }


     for (let opt in optConfig) {
     // Jetzt die inzwischen gefuellten Daten *dieses* Scripts ergaenzen...
        const __OPTCONFIG = optConfig[opt];
    scriptDB(__DBMOD.name, getValue(optSet, { }));
        const __PREINIT = getValue(__OPTCONFIG.PreInit, false, true);
        const __ISSHARED = getValue(__OPTCONFIG.Shared, false, true);


        if ((preInit === undefined) || (__PREINIT === preInit)) {
    __LOG[2](__DBDATA);
            const __CONFIG = getSharedConfig(__OPTCONFIG, opt);
}
            const __ALTACTION = getValue(__CONFIG.AltAction, __CONFIG.Action);
            // Gab es vorher einen Aufruf, der einen Stub-Eintrag erzeugt hat? Wurde ggfs. bereits geaendert...
            const __USESTUB = ((preInit === false) && __PREINIT);
            const __LOADED = (__USESTUB && optSet[opt].Loaded);
            const __VALUE = (__USESTUB ? optSet[opt].Value : undefined);


            optSet[opt] = {
// Holt die globalen Daten zu einem Modul aus der Scriptdatenbank
                'Item'      : opt,
// module: Gesetzte Optionen (und Config)
                'Config'    : __CONFIG,
// initValue: Falls angegeben, zugewiesener Startwert
                'Loaded'    : (__ISSHARED || __LOADED),
// return Daten zu diesem Modul
                'Value'    : initOptValue(__CONFIG, __VALUE),
function scriptDB(module, initValue = undefined) {
                'SetValue'  : __CONFIG.SetValue,
    const __NAMESPACE = __DBTOC.namespaces[module];
                'ReadOnly'  : (__ISSHARED || __CONFIG.ReadOnly),
    const __DBMODS = getProp(__DBDATA, __NAMESPACE, { });
                'Action'    : initOptAction(__CONFIG.Action, opt, optSet, __CONFIG),
 
                'AltAction' : initOptAction(__ALTACTION, opt, optSet, __CONFIG)
    if (initValue !== undefined) {
            };
        return (__DBMODS[module] = initValue);
        } else if (preInit) { // erstmal nur Stub
    } else {
            optSet[opt] = {
        return getProp(__DBMODS, module, { });
                'Item'      : opt,
                'Config'    : __OPTCONFIG,
                'Loaded'    : false,
                'Value'    : initOptValue(__OPTCONFIG),
                'ReadOnly'  : (__ISSHARED || __OPTCONFIG.ReadOnly)
            };
        }
     }
     }
}


    return optSet;
// ==================== Ende Abschnitt fuer die Scriptdatenbank ====================
}
 
// ==================== Ende Abschnitt fuer Speicher und die Scriptdatenbank ====================


    // Abhaengigkeiten:
// ==================== Abschnitt fuer das Benutzermenu ====================
    // ================
    // initOptions (PreInit):
    // restoreMemoryByOpt: PreInit oldStorage
    // getStoredCmds: restoreMemoryByOpt
    // runStoredCmds (beforeLoad): getStoredCmds
    // loadOptions (PreInit): PreInit
    // startMemoryByOpt: storage oldStorage
    // initScriptDB: startMemoryByOpt
    // initOptions (Rest): PreInit
    // getMyTeam callback (getOptPrefix): initTeam
    // __MYTEAM (initTeam): initOptions
    // renameOptions: getOptPrefix
    // runStoredCmds (afterLoad): getStoredCmds, renameOptions
    // loadOptions (Rest): PreInit/Rest runStoredCmds
    // updateScriptDB: startMemoryByOpt
    // showOptions: startMemoryByOpt renameOptions
    // buildMenu: showOptions
    // buildForm: showOptions


// Initialisiert die gesetzten Optionen und den Speicher und laedt die Optionen zum Start
// Zeigt den Eintrag im Menu einer Option
// optConfig: Konfiguration der Optionen
// val: Derzeitiger Wert der Option
// optSet: Platz fuer die gesetzten Optionen
// menuOn: Text zum Setzen im Menu
// return Gefuelltes Objekt mit den gesetzten Optionen
// funOn: Funktion zum Setzen
function startOptions(optConfig, optSet = undefined, classification = undefined) {
// keyOn: Hotkey zum Setzen im Menu
     optSet = initOptions(optConfig, optSet, true); // PreInit
// menuOff: Text zum Ausschalten im Menu
// funOff: Funktion zum Ausschalten
// keyOff: Hotkey zum Ausschalten im Menu
// return Promise von GM.registerMenuCommand()
function registerMenuOption(val, menuOn, funOn, keyOn, menuOff, funOff, keyOff) {
    const __ON  = (val ? '*' : "");
     const __OFF = (val ? "" : '*');


     // Memory Storage fuer vorherige Speicherung...
     __LOG[3]("OPTION " + __ON + menuOn + __ON + " / " + __OFF + menuOff + __OFF);
    myOptMemSize = getMemSize(myOptMem = restoreMemoryByOpt(optSet.oldStorage));


     // Zwischengespeicherte Befehle auslesen...
     if (val) {
     const __STOREDCMDS = getStoredCmds(myOptMem);
        return GM.registerMenuCommand(menuOff, funOff, keyOff).then(result => menuOn);
     } else {
        return GM.registerMenuCommand(menuOn, funOn, keyOn).then(result => menuOff);
    }
}


    // ... ermittelte Befehle ausführen...
// Zeigt den Eintrag im Menu einer Option mit Wahl des naechsten Wertes
     const __LOADEDCMDS = runStoredCmds(__STOREDCMDS, optSet, true); // BeforeLoad
// val: Derzeitiger Wert der Option
 
// arr: Array-Liste mit den moeglichen Optionen
     // Bisher noch nicht geladenene Optionen laden...
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
     loadOptions(optSet);
// fun: Funktion zum Setzen des naechsten Wertes
// key: Hotkey zum Setzen des naechsten Wertes im Menu
// return Promise von GM.registerMenuCommand()
function registerNextMenuOption(val, arr, menu, fun, key) {
     const __MENU = substParam(menu, val);
    let options = "OPTION " + __MENU;
 
    for (let value of arr) {
        if (value === val) {
            options += " / *" + value + '*';
        } else {
            options += " / " + value;
        }
     }
     __LOG[3](options);


     // Memory Storage fuer naechste Speicherung...
     return GM.registerMenuCommand(__MENU, fun, key).then(result => __MENU);
    myOptMemSize = getMemSize(myOptMem = startMemoryByOpt(optSet.storage, optSet.oldStorage));
}


    // Globale Daten ermitteln...
// Zeigt den Eintrag im Menu einer Option, falls nicht hidden
     initScriptDB(optSet);
// val: Derzeitiger Wert der Option
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
// fun: Funktion zum Setzen des naechsten Wertes
// key: Hotkey zum Setzen des naechsten Wertes im Menu
// hidden: Angabe, ob Menupunkt nicht sichtbar sein soll (Default: sichtbar)
// serial: Serialization fuer komplexe Daten
// return Promise von GM.registerMenuCommand() (oder String-Version des Wertes)
function registerDataOption(val, menu, fun, key, hidden = false, serial = true) {
    const __VALUE = ((serial && (val !== undefined)) ? safeStringify(val) : val);
    const __MENU = substParam(menu, __VALUE);
     const __OPTIONS = (hidden ? "HIDDEN " : "") + "OPTION " + __MENU +
                      getValue(__VALUE, "", " = " + __VALUE);


     optSet = initOptions(optConfig, optSet, false); // Rest
     __LOG[hidden ? 4 : 3](__OPTIONS);


     if (classification !== undefined) {
     if (hidden) {
         // Umbenennungen durchfuehren...
         return Promise.resolve(__VALUE);
         classification.renameOptions();
    } else {
         return GM.registerMenuCommand(__MENU, fun, key).then(result => __MENU);
     }
     }
    // ... ermittelte Befehle ausführen...
    runStoredCmds(__LOADEDCMDS, optSet, false);  // Rest
    // Als globale Daten speichern...
    updateScriptDB(optSet);
    return optSet;
}
}


// Installiert die Visualisierung und Steuerung der Optionen
// Zeigt den Eintrag im Menu einer Option
// optSet: Platz fuer die gesetzten Optionen
// opt: Config und Value der Option
// optParams: Eventuell notwendige Parameter zur Initialisierung
// return Promise von GM.registerMenuCommand() (oder String-Version des Wertes)
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
function registerOption(opt) {
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
     const __CONFIG = getOptConfig(opt);
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
    const __VALUE = getOptValue(opt);
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
     const __LABEL = __CONFIG.Label;
// 'formWidth': Anzahl der Elemente pro Zeile
    const __ACTION = opt.Action;
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
    const __HOTKEY = __CONFIG.Hotkey;
function showOptions(optSet = undefined, optParams = { 'hideMenu' : false }) {
    const __HIDDEN = __CONFIG.HiddenMenu;
     if (! optParams.hideMenu) {
    const __SERIAL = __CONFIG.Serial;
        buildMenu(optSet);
     }


     if ((optParams.menuAnchor !== undefined) && (myOptMem !== __OPTMEMINAKTIVE)) {
     if (! __CONFIG.HiddenMenu) {
         buildForm(optParams.menuAnchor, optSet, optParams);
        switch (__CONFIG.Type) {
        case __OPTTYPES.MC : return registerNextMenuOption(__VALUE, __CONFIG.Choice, __LABEL, __ACTION, __HOTKEY);
        case __OPTTYPES.SW : return registerMenuOption(__VALUE, __LABEL, __ACTION, __HOTKEY,
                                                      __CONFIG.AltLabel, __ACTION, __CONFIG.AltHotkey);
        case __OPTTYPES.TF : return registerMenuOption(__VALUE, __LABEL, __ACTION, __HOTKEY,
                                                      __CONFIG.AltLabel, opt.AltAction, __CONFIG.AltHotkey);
        case __OPTTYPES.SD : return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
        case __OPTTYPES.SI : return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
        default :            return Promise.resolve(__VALUE);
        }
    } else {
         // Nur Anzeige im Log...
        return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
     }
     }
}
}


// Setzt eine Option auf einen vorgegebenen Wert
// ==================== Ende Abschnitt fuer das Benutzermenu ====================
// Fuer kontrollierte Auswahl des Values siehe setNextOpt()
// opt: Config und vorheriger Value der Option
// value: (Bei allen Typen) Zu setzender Wert
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Wert
function setOpt(opt, value, reload = false) {
    return setOptValue(opt, setStored(getOptName(opt), value, reload, getOptConfig(opt).Serial));
}


// Ermittelt die naechste moegliche Option
// Initialisiert die gesetzten Option
// opt: Config und Value der Option
// config: Konfiguration der Option
// value: Ggfs. zu setzender Wert
// setValue: Zu uebernehmender Default-Wert (z.B. der jetzt gesetzte)
// return Zu setzender Wert
// return Initialwert der gesetzten Option
function getNextOpt(opt, value = undefined) {
function initOptValue(config, setValue = undefined) {
     const __CONFIG = getOptConfig(opt);
     let value = getValue(setValue, config.Default); // Standard
     const __VALUE = getOptValue(opt, value);
 
     if (config.SharedData !== undefined) {
        value = config.SharedData;
    }


     switch (__CONFIG.Type) {
     switch (config.Type) {
     case __OPTTYPES.MC : return getValue(value, getNextValue(__CONFIG.Choice, __VALUE));
     case __OPTTYPES.MC : if ((value === undefined) && (config.Choice !== undefined)) {
     case __OPTTYPES.SW : return getValue(value, ! __VALUE);
                            value = config.Choice[0];
     case __OPTTYPES.TF : return getValue(value, ! __VALUE);
                        }
     case __OPTTYPES.SD : return getValue(value, __VALUE);
                        break;
     case __OPTTYPES.SW : break;
     case __OPTTYPES.TF : break;
     case __OPTTYPES.SD : config.Serial = true;
                        break;
     case __OPTTYPES.SI : break;
     case __OPTTYPES.SI : break;
     default :            break;
     default :            break;
     }
     }


     return __VALUE;
     if (config.Serial || config.Hidden) {
}
        config.HiddenMenu = true;
    }


// Setzt die naechste moegliche Option
     return value;
// opt: Config und Value der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Wert
function setNextOpt(opt, value = undefined, reload = false) {
     return setOpt(opt, getNextOpt(opt, value), reload);
}
}


// Setzt die naechste moegliche Option oder fragt ab einer gewissen Anzahl interaktiv ab
// Initialisiert die Menue-Funktion einer Option
// opt: Config und Value der Option
// optAction: Typ der Funktion
// value: Default fuer ggfs. zu setzenden Wert
// item: Key der Option
// reload: Seite mit neuem Wert neu laden
// optSet: Platz fuer die gesetzten Optionen (und Config)
// freeValue: Angabe, ob Freitext zugelassen ist (Default: false)
// optConfig: Konfiguration der Option
// minChoice: Ab wievielen Auswahlmoeglichkeiten soll abgefragt werden? (Default: 3)
// return Funktion fuer die Option
// return Gesetzter Wert
function initOptAction(optAction, item = undefined, optSet = undefined, optConfig = undefined) {
function promptNextOpt(opt, value = undefined, reload = false, freeValue = false, selValue = true, minChoice = 3) {
    let fun;
    const __CONFIG = getOptConfig(opt);
 
    const __CHOICE = __CONFIG.Choice;
    if (optAction !== undefined) {
        const __CONFIG = ((optConfig !== undefined) ? optConfig : getOptConfig(getOptByName(optSet, item)));
        const __RELOAD = getValue(getValue(__CONFIG, { }).ActionReload, true);


    if (value || (! __CHOICE) || (__CHOICE.length < minChoice)) {
        switch (optAction) {
         return setNextOpt(opt, value, reload);
        case __OPTACTION.SET : fun = function() {
                                      return setOptByName(optSet, item, __CONFIG.SetValue, __RELOAD).catch(defaultCatch);
                                  };
                              break;
        case __OPTACTION.NXT : fun = function() {
                                      return promptNextOptByName(optSet, item, __CONFIG.SetValue, __RELOAD,
                                                  __CONFIG.FreeValue, __CONFIG.SelValue, __CONFIG.MinChoice).catch(defaultCatch);
                                  };
                              break;
         case __OPTACTION.RST : fun = function() {
                                      return resetOptions(optSet, __RELOAD).then(
                                              result => __LOG[3]("RESETTING (" + result + ")..."),
                                              defaultCatch);
                                  };
                              break;
        default :              break;
        }
     }
     }


     const __VALUE = getOptValue(opt, value);
     return fun;
}


    try {
// Gibt fuer einen 'Shared'-Eintrag eine ObjRef zurueck
        const __NEXTVAL = getNextValue(__CHOICE, __VALUE);
// shared: Object mit den Angaben 'namespace', 'module' und ggfs. 'item'
         let message = "";
// item: Key der Option
// return ObjRef, die das Ziel definiert
function getSharedRef(shared, item = undefined) {
    if (shared === undefined) {
         return undefined;
    }


        if (selValue) {
    const __OBJREF = new ObjRef(__DBDATA); // Gemeinsame Daten
            for (let index = 0; index < __CHOICE.length; index++) {
    const __PROPS = [ 'namespace', 'module', 'item' ];
                message += (index + 1) + ") " + __CHOICE[index] + '\n';
    const __DEFAULTS = [ __DBMOD.namespace, __DBMOD.name, item ];
            }
            message += "\nNummer eingeben:";
        } else {
            message = __CHOICE.join(" / ") + "\n\nWert eingeben:";
        }


         const __ANSWER = prompt(message, __NEXTVAL);
    for (let stage in __PROPS) {
        const __DEFAULT = __DEFAULTS[stage];
         const __PROP = __PROPS[stage];
        const __NAME = shared[__PROP];


         if (__ANSWER) {
         if (__NAME === '$') {
             const __INDEX = parseInt(__ANSWER, 10) - 1;
             break;
            let nextVal = (selValue ? __CHOICE[__INDEX] : undefined);
        }


            if (nextVal === undefined) {
        __OBJREF.chDir(getValue(__NAME, __DEFAULT));
                const __VALTYPE = getValue(__CONFIG.ValType, 'String');
                const __CASTVAL = this[__VALTYPE](__ANSWER);
 
                if (freeValue || (~ __CHOICE.indexOf(__CASTVAL))) {
                    nextVal = __CASTVAL;
                }
            }
 
            if (nextVal !== __VALUE) {
                if (nextVal) {
                    return setOpt(opt, nextVal, reload);
                }
 
                const __LABEL = __CONFIG.Label.replace('$', __VALUE);
 
                showAlert(__LABEL, "Ung\xFCltige Eingabe: " + __ANSWER);
            }
        }
    } catch (ex) {
        __LOG[1]("promptNextOpt: " + ex.message);
     }
     }


     return __VALUE;
     return __OBJREF;
}
}


// Setzt eine Option auf einen vorgegebenen Wert (Version mit Key)
// Gibt diese Config oder, falls 'Shared', ein Referenz-Objekt mit gemeinsamen Daten zurueck
// Fuer kontrollierte Auswahl des Values siehe setNextOptByName()
// optConfig: Konfiguration der Option
// optSet: Platz fuer die gesetzten Optionen (und Config)
// item: Key der Option
// item: Key der Option
// return Entweder optConfig oder gemergete Daten auf Basis des in 'Shared' angegebenen Objekts
// value: (Bei allen Typen) Zu setzender Wert
function getSharedConfig(optConfig, item = undefined) {
// reload: Seite mit neuem Wert neu laden
     let config = getValue(optConfig, { });
// return Gesetzter Wert
    const __SHARED = config.Shared;
function setOptByName(optSet, item, value, reload = false) {
     const __OPT = getOptByName(optSet, item);


     return setOpt(__OPT, value, reload);
     if (__SHARED !== undefined) {
}
        const __OBJREF = getSharedRef(__SHARED, item); // Gemeinsame Daten


// Ermittelt die naechste moegliche Option (Version mit Key)
        if (getValue(__SHARED.item, '$') !== '$') { // __OBJREF ist ein Item
// optSet: Platz fuer die gesetzten Optionen (und Config)
            const __REF = valueOf(__OBJREF);
// item: Key der Option
// value: Default fuer ggfs. zu setzenden Wert
// return Zu setzender Wert
function getNextOptByName(optSet, item, value = undefined) {
    const __OPT = getOptByName(optSet, item);


    return getNextOpt(__OPT, value);
            config = { };  // Neu aufbauen...
}
            addProps(config, getOptConfig(__REF));
            addProps(config, optConfig);
            config.setConst('SharedData', getOptValue(__REF), false);  // Wert muss schon da sein, NICHT nachladen, sonst ggfs. Promise
        } else {  // __OBJREF enthaelt die Daten selbst
            if (! config.Name) {
                config.Name = __OBJREF.getPath();
            }
            config.setConst('SharedData', __OBJREF); // Achtung: Ggfs. zirkulaer!
        }
    }


// Setzt die naechste moegliche Option (Version mit Key)
     return config;
// optSet: Platz fuer die gesetzten Optionen (und Config)
// item: Key der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Wert
function setNextOptByName(optSet, item, value = undefined, reload = false) {
    const __OPT = getOptByName(optSet, item);
 
     return setNextOpt(__OPT, value, reload);
}
}


// Setzt die naechste moegliche Option oder fragt ab einer gewissen Anzahl interaktiv ab (Version mit Key)
// Initialisiert die gesetzten Optionen
// optSet: Platz fuer die gesetzten Optionen (und Config)
// optConfig: Konfiguration der Optionen
// item: Key der Option
// optSet: Platz fuer die gesetzten Optionen
// value: Default fuer ggfs. zu setzenden Wert
// preInit: Vorinitialisierung einzelner Optionen mit 'PreInit'-Attribut
// reload: Seite mit neuem Wert neu laden
// return Gefuelltes Objekt mit den gesetzten Optionen
// freeValue: Angabe, ob Freitext zugelassen ist (Default: false)
function initOptions(optConfig, optSet = undefined, preInit = undefined) {
// minChoice: Ab wievielen Auswahlmoeglichkeiten soll abgefragt werden? (Default: 3)
     let value;
// return Gesetzter Wert
function promptNextOptByName(optSet, item, value = undefined, reload = false, freeValue = false, selValue = true, minChoice = 3) {
     const __OPT = getOptByName(optSet, item);


     return promptNextOpt(__OPT, value, reload, freeValue, selValue, minChoice);
     if (optSet === undefined) {
}
        optSet = { };
 
// Baut das Benutzermenu auf
// optSet: Gesetzte Optionen
function buildMenu(optSet) {
    __LOG[3]("buildMenu()");
 
    for (let opt in optSet) {
        registerOption(optSet[opt]);
     }
     }
}


// Invalidiert eine (ueber Menu) gesetzte Option
    for (let opt in optConfig) {
// opt: Zu invalidierende Option
        const __OPTCONFIG = optConfig[opt];
// force: Invalidiert auch Optionen mit 'AutoReset'-Attribut
        const __PREINIT = getValue(__OPTCONFIG.PreInit, false, true);
function invalidateOpt(opt, force = false) {
         const __ISSHARED = getValue(__OPTCONFIG.Shared, false, true);
    if (! opt.ReadOnly) {
         const __CONFIG = getOptConfig(opt);


         // Wert "ungeladen"...
         if ((preInit === undefined) || (__PREINIT === preInit)) {
        opt.Loaded = (force || ! __CONFIG.AutoReset);
            const __CONFIG = getSharedConfig(__OPTCONFIG, opt);
            const __ALTACTION = getValue(__CONFIG.AltAction, __CONFIG.Action);
            // Gab es vorher einen Aufruf, der einen Stub-Eintrag erzeugt hat, und wurden Daten geladen?
            const __LOADED = ((preInit === false) && optSet[opt].Loaded);
            const __PROMISE = ((__LOADED || ! optSet[opt]) ? undefined : optSet[opt].Promise);
            const __VALUE = (__LOADED ? optSet[opt].Value : undefined);


        if (opt.Loaded && __CONFIG.AutoReset) {
            optSet[opt] = {
            // Nur zuruecksetzen, gilt als geladen...
                'Item'      : opt,
            setOptValue(opt, initOptValue(__CONFIG));
                'Config'    : __CONFIG,
        }
                'Loaded'    : (__ISSHARED || __LOADED),
     }
                'Promise'  : __PROMISE,
}
                'Value'     : initOptValue(__CONFIG, __VALUE),
 
                'SetValue'  : __CONFIG.SetValue,
// Invalidiert die (ueber Menu) gesetzten Optionen
                'ReadOnly'  : (__ISSHARED || __CONFIG.ReadOnly),
// optSet: Set mit den Optionen
                'Action'   : initOptAction(__CONFIG.Action, opt, optSet, __CONFIG),
// force: Invalidiert auch Optionen mit 'AutoReset'-Attribut
                'AltAction' : initOptAction(__ALTACTION, opt, optSet, __CONFIG)
// return Set mit den geladenen Optionen
            };
function invalidateOpts(optSet, force = false) {
        } else if (preInit) { // erstmal nur Stub
    for (let opt in optSet) {
            optSet[opt] = {
        const __OPT = optSet[opt];
                'Item'      : opt,
 
                'Config'    : __OPTCONFIG,
        if (__OPT.Loaded) {
                'Loaded'    : false,
             invalidateOpt(__OPT, force);
                'Promise'  : undefined,
                'Value'    : initOptValue(__OPTCONFIG),
                'ReadOnly'  : (__ISSHARED || __OPTCONFIG.ReadOnly)
             };
         }
         }
     }
     }
Zeile 2.600: Zeile 2.803:
}
}


// Laedt eine (ueber Menu) gesetzte Option
    // Abhaengigkeiten:
// opt: Zu ladende Option
    // ================
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
    // initOptions (PreInit):
// return Gesetzter Wert der gelandenen Option
    // restoreMemoryByOpt: PreInit oldStorage
function loadOption(opt, force = false) {
    // getStoredCmds: restoreMemoryByOpt
     const __CONFIG = getOptConfig(opt);
    // runStoredCmds (beforeLoad): getStoredCmds
     const __ISSHARED = getValue(__CONFIG.Shared, false, true);
     // loadOptions (PreInit): PreInit
     const __NAME = getOptName(opt);
    // startMemoryByOpt: storage oldStorage
     const __DEFAULT = getOptValue(opt, undefined, false, false);
    // initScriptDB: startMemoryByOpt
     let value;
     // initOptions (Rest): PreInit
 
     // getMyTeam callback (getOptPrefix): initTeam
     if (opt.Loaded && ! __ISSHARED) {
     // __MYTEAM (initTeam): initOptions
        __LOG[1]("Error: Oprion '" + __NAME + "' bereits geladen!");
     // renameOptions: getOptPrefix
     }
    // runStoredCmds (afterLoad): getStoredCmds, renameOptions
 
     // loadOptions (Rest): PreInit/Rest runStoredCmds
     if (opt.ReadOnly || __ISSHARED) {
    // updateScriptDB: startMemoryByOpt
        value = __DEFAULT;
     // showOptions: startMemoryByOpt renameOptions
     } else if (! force && __CONFIG.AutoReset) {
    // buildMenu: showOptions
        value = initOptValue(__CONFIG);
     // buildForm: showOptions
     } else {
 
        value = (__CONFIG.Serial ?
// Initialisiert die gesetzten Optionen und den Speicher und laedt die Optionen zum Start
                        deserialize(__NAME, __DEFAULT) :
// optConfig: Konfiguration der Optionen
                        GM_getValue(__NAME, __DEFAULT));
// optSet: Platz fuer die gesetzten Optionen
    }
// return Promise auf gefuelltes Objekt mit den gesetzten Optionen
async function startOptions(optConfig, optSet = undefined, classification = undefined) {
    optSet = initOptions(optConfig, optSet, true); // PreInit
 
     // Memory Storage fuer vorherige Speicherung...
    myOptMemSize = getMemSize(myOptMem = await restoreMemoryByOpt(optSet.oldStorage));
 
     // Zwischengespeicherte Befehle auslesen...
    const __STOREDCMDS = getStoredCmds(myOptMem);


     __LOG[5]("LOAD " + __NAME + ": " + __LOG.changed(__DEFAULT, value));
     // ... ermittelte Befehle ausfuehren...
    const __LOADEDCMDS = await runStoredCmds(__STOREDCMDS, optSet, true); // BeforeLoad


     // Wert als geladen markieren...
     // Bisher noch nicht geladenene Optionen laden...
     opt.Loaded = true;
     await loadOptions(optSet);


     // Wert intern setzen...
     // Memory Storage fuer naechste Speicherung...
     return setOptValue(opt, value);
     myOptMemSize = getMemSize(myOptMem = startMemoryByOpt(optSet.storage, optSet.oldStorage));
}
 
    // Globale Daten ermitteln...
    initScriptDB(optSet);


// Laedt die (ueber Menu) gesetzten Optionen
    optSet = initOptions(optConfig, optSet, false); // Rest
// optSet: Set mit den Optionen
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Set mit den geladenen Optionen
function loadOptions(optSet, force = false) {
    for (let opt in optSet) {
        const __OPT = optSet[opt];


        if (! __OPT.Loaded) {
    if (classification !== undefined) {
            loadOption(__OPT, force);
        // Umbenennungen durchfuehren...
        }
        await classification.renameOptions();
     }
     }
    // ... ermittelte Befehle ausfuehren...
    await runStoredCmds(__LOADEDCMDS, optSet, false);  // Rest
    // Als globale Daten speichern...
    updateScriptDB(optSet);


     return optSet;
     return optSet;
}
}


// Entfernt eine (ueber Menu) gesetzte Option (falls nicht 'Permanent')
// Installiert die Visualisierung und Steuerung der Optionen
// opt: Gesetzte Option
// optSet: Platz fuer die gesetzten Optionen
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
// optParams: Eventuell notwendige Parameter zur Initialisierung
// reset: Setzt bei Erfolg auf Initialwert der Option
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
function deleteOption(opt, force = false, reset = true) {
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
     const __CONFIG = getOptConfig(opt);
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return Liefert die gesetzten Optionen zurueck
function showOptions(optSet = undefined, optParams = { 'hideMenu' : false }) {
     if (! optParams.hideMenu) {
        buildMenu(optSet).then(() => __LOG[3]("Menu OK"));
    }


     if (force || ! __CONFIG.Permanent) {
     if ((optParams.menuAnchor !== undefined) && (myOptMem !== __OPTMEMINAKTIVE)) {
         const __NAME = getOptName(opt);
         buildForm(optParams.menuAnchor, optSet, optParams);
    }


        __LOG[4]("DELETE " + __NAME);
    return optSet;
 
        GM_deleteValue(__NAME);
 
        if (reset) {
            setOptValue(opt, initOptValue(__CONFIG));
        }
    }
}
}


// Entfernt die (ueber Menu) gesetzten Optionen (falls nicht 'Permanent')
// Setzt eine Option auf einen vorgegebenen Wert
// optSet: Gesetzte Optionen
// Fuer kontrollierte Auswahl des Values siehe setNextOpt()
// optSelect: Liste von ausgewaehlten Optionen, true = entfernen, false = nicht entfernen
// opt: Config und vorheriger Value der Option
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
// value: (Bei allen Typen) Zu setzender Wert
// reset: Setzt bei Erfolg auf Initialwert der Option
// reload: Seite mit neuem Wert neu laden
function deleteOptions(optSet, optSelect = undefined, force = false, reset = true) {
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
    const __DELETEALL = (optSelect === undefined) || (optSelect === true);
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
    const __OPTSELECT = getValue(optSelect, { });
// return Gesetzter Wert
 
function setOpt(opt, value, reload = false, onFulfilled = undefined, onRejected = undefined) {
     for (let opt in optSet) {
     return setOptValue(opt, setStored(getOptName(opt), value, reload, getOptConfig(opt).Serial, onFulfilled, onRejected));
        if (getValue(__OPTSELECT[opt], __DELETEALL)) {
            deleteOption(optSet[opt], force, reset);
        }
    }
}
}


// Benennt eine Option um und laedt sie ggfs. nach
// Ermittelt die naechste moegliche Option
// opt: Gesetzte Option
// opt: Config und Value der Option
// name: Neu zu setzender Name (Speicheradresse)
// value: Ggfs. zu setzender Wert
// reload: Wert nachladen statt beizubehalten
// return Zu setzender Wert
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
function getNextOpt(opt, value = undefined) {
// return Umbenannte Option
    const __CONFIG = getOptConfig(opt);
function renameOption(opt, name, reload = false, force = false) {
     const __VALUE = getOptValue(opt, value);
     const __NAME = getOptName(opt);


     if (__NAME !== name) {
     switch (__CONFIG.Type) {
        deleteOption(opt, true, ! reload);
    case __OPTTYPES.MC : return getValue(value, getNextValue(__CONFIG.Choice, __VALUE));
 
    case __OPTTYPES.SW : return getValue(value, ! __VALUE);
        setOptName(opt, name);
    case __OPTTYPES.TF : return getValue(value, ! __VALUE);
 
    case __OPTTYPES.SD : return getValue(value, __VALUE);
        if (reload) {
    case __OPTTYPES.SI : break;
            loadOption(opt, force);
    default :            break;
        }
     }
     }


     return opt;
     return __VALUE;
}
}


// Ermittelt einen neuen Namen mit einem Prefix. Parameter fuer renameOptions()
// Setzt die naechste moegliche Option
// name: Gesetzter Name (Speicheradresse)
// opt: Config und Value der Option
// prefix: Prefix, das vorangestellt werden soll
// value: Default fuer ggfs. zu setzenden Wert
// return Neu zu setzender Name (Speicheradresse)
// reload: Seite mit neuem Wert neu laden
function prefixName(name, prefix) {
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
     return (prefix + name);
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function setNextOpt(opt, value = undefined, reload = false, onFulfilled = undefined, onRejected = undefined) {
     return setOpt(opt, getNextOpt(opt, value), reload, onFulfilled, onRejected);
}
}


// Ermittelt einen neuen Namen mit einem Postfix. Parameter fuer renameOptions()
// Setzt die naechste moegliche Option oder fragt ab einer gewissen Anzahl interaktiv ab
// name: Gesetzter Name (Speicheradresse)
// opt: Config und Value der Option
// postfix: Postfix, das angehaengt werden soll
// value: Default fuer ggfs. zu setzenden Wert
// return Neu zu setzender Name (Speicheradresse)
// reload: Seite mit neuem Wert neu laden
function postfixName(name, postfix) {
// freeValue: Angabe, ob Freitext zugelassen ist (Default: false)
     return (name + postfix);
// minChoice: Ab wievielen Auswahlmoeglichkeiten soll abgefragt werden? (Default: 3)
}
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function promptNextOpt(opt, value = undefined, reload = false, freeValue = false, selValue = true, minChoice = 3, onFulfilled = undefined, onRejected = undefined) {
     const __CONFIG = getOptConfig(opt);
    const __CHOICE = __CONFIG.Choice;


// Benennt selektierte Optionen nach einem Schema um und laedt sie ggfs. nach
    if (value || (! __CHOICE) || (__CHOICE.length < minChoice)) {
// optSet: Gesetzte Optionen
         return setNextOpt(opt, value, reload, onFulfilled, onRejected);
// optSelect: Liste von ausgewaehlten Optionen, true = nachladen, false = nicht nachladen
// 'reload': Option nachladen?
// 'force': Option auch mit 'AutoReset'-Attribut nachladen?
// renameParam: Wird an renameFun uebergeen
// renameFun: function(name, param) zur Ermittlung des neuen Namens
// - name: Neu zu setzender Name (Speicheradresse)
// - param: Parameter "renameParam" von oben, z.B. Prefix oder Postfix
function renameOptions(optSet, optSelect, renameParam = undefined, renameFun = prefixName) {
    if (renameFun === undefined) {
         __LOG[1]("RENAME: Illegale Funktion!");
     }
     }
    for (let opt in optSelect) {
        const __OPTPARAMS = optSelect[opt];
        const __OPT = optSet[opt];


        if (__OPT === undefined) {
    const __VALUE = getOptValue(opt, value);
            __LOG[1]("RENAME: Option '" + opt + "' nicht gefunden!");
        } else {
            const __NAME = getOptName(__OPT);
            const __NEWNAME = renameFun(__NAME, renameParam);
            const __ISSCALAR = ((typeof __OPTPARAMS) === 'boolean');
            // Laedt die unter dem neuen Namen gespeicherten Daten nach?
            const __RELOAD = (__ISSCALAR ? __OPTPARAMS : __OPTPARAMS.reload);
            // Laedt auch Optionen mit 'AutoReset'-Attribut?
            const __FORCE = (__ISSCALAR ? true : __OPTPARAMS.force);


            renameOption(__OPT, __NEWNAME, __RELOAD, __FORCE);
    try {
         }
        const __NEXTVAL = getNextValue(__CHOICE, __VALUE);
    }
         let message = "";
}


// Setzt die Optionen in optSet auf die "Werkseinstellungen" des Skripts
        if (selValue) {
// optSet: Gesetzte Optionen
            for (let index = 0; index < __CHOICE.length; index++) {
// reload: Seite mit "Werkseinstellungen" neu laden
                message += (index + 1) + ") " + __CHOICE[index] + '\n';
function resetOptions(optSet, reload = true) {
            }
    // Alle (nicht 'Permanent') gesetzten Optionen entfernen...
            message += "\nNummer oder Wert eingeben:";
    deleteOptions(optSet, true, false, ! reload);
        } else {
            message = __CHOICE.join(" / ") + "\n\nWert eingeben:";
        }


    if (reload) {
         const __ANSWER = prompt(message, __NEXTVAL);
        // ... und Seite neu laden (mit "Werkseinstellungen")...
         window.location.reload();
    }
}


// ==================== Abschnitt fuer diverse Utilities ====================
        if (__ANSWER) {
            const __INDEX = parseInt(__ANSWER, 10) - 1;
            let nextVal = (selValue ? __CHOICE[__INDEX] : undefined);


// Legt Input-Felder in einem Form-Konstrukt an, falls noetig
            if (nextVal === undefined) {
// form: <form>...</form>
                const __VALTYPE = getValue(__CONFIG.ValType, 'String');
// props: Map von name:value-Paaren
                const __CASTVAL = this[__VALTYPE](__ANSWER);
// type: Typ der Input-Felder (Default: unsichtbare Daten)
// return Ergaenztes Form-Konstrukt
function addInputField(form, props, type = "hidden") {
    for (let fieldName in props) {
        let field = form[fieldName];
        if (! field) {
            field = document.createElement("input");
            field.type = type;
            field.name = fieldName;
            form.appendChild(field);
        }
        field.value = props[fieldName];
    }


    return form;
                if (freeValue || (~ __CHOICE.indexOf(__CASTVAL))) {
}
                    nextVal = __CASTVAL;
                }
            }


// Legt unsichtbare Input-Daten in einem Form-Konstrukt an, falls noetig
            if (nextVal !== __VALUE) {
// form: <form>...</form>
                if (nextVal) {
// props: Map von name:value-Paaren
                    return setOpt(opt, nextVal, reload, onFulfilled, onRejected);
// return Ergaenztes Form-Konstrukt
                }
function addHiddenField(form, props) {
    return addInputField(form, props, "hidden");
}


// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
                const __LABEL = substParam(__CONFIG.Label, __VALUE);
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function addEvent(obj, type, callback, capture = false) {
    if (obj.addEventListener) {
        return obj.addEventListener(type, callback, capture);
    } else if (obj.attachEvent) {
        return obj.attachEvent("on" + type, callback);
    } else {
        __LOG[1]("Could not add " + type + " event:");
        __LOG[2](callback);


         return false;
                showAlert(__LABEL, "Ung\xFCltige Eingabe: " + __ANSWER);
            }
         }
    } catch (ex) {
        __LOG[0]("promptNextOpt: " + ex.message);
     }
     }
}


// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
     return __VALUE;
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function removeEvent(obj, type, callback, capture = false) {
    if (obj.removeEventListener) {
        return obj.removeEventListener(type, callback, capture);
    } else if (obj.detachEvent) {
        return obj.detachEvent("on" + type, callback);
     } else {
        __LOG[1]("Could not remove " + type + " event:");
        __LOG[2](callback);
 
        return false;
    }
}
}


// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
// Setzt eine Option auf einen vorgegebenen Wert (Version mit Key)
// id: ID des betroffenen Eingabeelements
// Fuer kontrollierte Auswahl des Values siehe setNextOptByName()
// type: Name des Events, z.B. "click"
// optSet: Platz fuer die gesetzten Optionen (und Config)
// callback: Funktion als Reaktion
// item: Key der Option
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// value: (Bei allen Typen) Zu setzender Wert
// return false bei Misserfolg
// reload: Seite mit neuem Wert neu laden
function addDocEvent(id, type, callback, capture = false) {
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
     const __OBJ = document.getElementById(id);
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function setOptByName(optSet, item, value, reload = false, onFulfilled = undefined, onRejected = undefined) {
     const __OPT = getOptByName(optSet, item);


     return addEvent(__OBJ, type, callback, capture);
     return setOpt(__OPT, value, reload, onFulfilled, onRejected);
}
}


// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
// Ermittelt die naechste moegliche Option (Version mit Key)
// id: ID des betroffenen Eingabeelements
// optSet: Platz fuer die gesetzten Optionen (und Config)
// type: Name des Events, z.B. "click"
// item: Key der Option
// callback: Funktion als Reaktion
// value: Default fuer ggfs. zu setzenden Wert
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return Zu setzender Wert
// return false bei Misserfolg
function getNextOptByName(optSet, item, value = undefined) {
function removeDocEvent(id, type, callback, capture = false) {
     const __OPT = getOptByName(optSet, item);
     const __OBJ = document.getElementById(id);


     return removeEvent(__OBJ, type, callback, capture);
     return getNextOpt(__OPT, value);
}
}


// Hilfsfunktion fuer die Ermittlung eines Elements der Seite
// Setzt die naechste moegliche Option (Version mit Key)
// name: Name des Elements (siehe "name=")
// optSet: Platz fuer die gesetzten Optionen (und Config)
// index: Laufende Nummer des Elements (0-based), Default: 0
// item: Key der Option
// doc: Dokument (document)
// value: Default fuer ggfs. zu setzenden Wert
// return Gesuchtes Element mit der lfd. Nummer index oder undefined (falls nicht gefunden)
// reload: Seite mit neuem Wert neu laden
function getElement(name, index = 0, doc = document) {
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
     const __TAGS = document.getElementsByName(name);
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
    const __TABLE = (__TAGS === undefined) ? undefined : __TAGS[index];
// return Gesetzter Wert
function setNextOptByName(optSet, item, value = undefined, reload = false, onFulfilled = undefined, onRejected = undefined) {
     const __OPT = getOptByName(optSet, item);


     return __TABLE;
     return setNextOpt(__OPT, value, reload, onFulfilled, onRejected);
}
}


// Hilfsfunktion fuer die Ermittlung eines Elements der Seite (Default: Tabelle)
// Setzt die naechste moegliche Option oder fragt ab einer gewissen Anzahl interaktiv ab (Version mit Key)
// index: Laufende Nummer des Elements (0-based)
// optSet: Platz fuer die gesetzten Optionen (und Config)
// tag: Tag des Elements ("table")
// item: Key der Option
// doc: Dokument (document)
// value: Default fuer ggfs. zu setzenden Wert
// return Gesuchtes Element oder undefined (falls nicht gefunden)
// reload: Seite mit neuem Wert neu laden
function getTable(index, tag = "table", doc = document) {
// freeValue: Angabe, ob Freitext zugelassen ist (Default: false)
     const __TAGS = document.getElementsByTagName(tag);
// minChoice: Ab wievielen Auswahlmoeglichkeiten soll abgefragt werden? (Default: 3)
    const __TABLE = (__TAGS === undefined) ? undefined : __TAGS[index];
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function promptNextOptByName(optSet, item, value = undefined, reload = false, freeValue = false, selValue = true, minChoice = 3, onFulfilled = undefined, onRejected = undefined) {
     const __OPT = getOptByName(optSet, item);


     return __TABLE;
     return promptNextOpt(__OPT, value, reload, freeValue, selValue, minChoice, onFulfilled, onRejected);
}
}


// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
// Baut das Benutzermenu auf (asynchron im Hintergrund)
// index: Laufende Nummer des Elements (0-based)
// optSet: Gesetzte Optionen
// doc: Dokument (document)
// return Promise auf void
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
async function buildMenu(optSet) {
function getRows(index, doc = document) {
     __LOG[3]("buildMenu()");
     const __TABLE = getTable(index, "table", doc);
    const __ROWS = (__TABLE === undefined) ? undefined : __TABLE.rows;


     return __ROWS;
     for (let opt in optSet) {
        await registerOption(optSet[opt]).then(
                result => __LOG[6](`REGISTEROPTION[${opt}] = ${result}`),
                defaultCatch);
    }
}
}


// ==================== Abschnitt fuer Optionen auf der Seite ====================
// Invalidiert eine (ueber Menu) gesetzte Option
// opt: Zu invalidierende Option
// force: Invalidiert auch Optionen mit 'AutoReset'-Attribut
// return Promise auf resultierenden Wert
function invalidateOpt(opt, force = false) {
    return Promise.resolve(opt.Promise).then(value => {
            if (opt.Loaded && ! opt.ReadOnly) {
                const __CONFIG = getOptConfig(opt);
 
                // Wert "ungeladen"...
                opt.Loaded = (force || ! __CONFIG.AutoReset);
 
                if (opt.Loaded && __CONFIG.AutoReset) {
                    // Nur zuruecksetzen, gilt als geladen...
                    setOptValue(opt, initOptValue(__CONFIG));
                }
            }
 
            return getOptValue(opt);
        }, defaultCatch);
}


// Liefert den Funktionsaufruf zur Option als String
// Invalidiert die (ueber Menu) gesetzten Optionen
// opt: Auszufuehrende Option
// optSet: Object mit den Optionen
// isAlt: Angabe, ob AltAction statt Action gemeint ist
// force: Invalidiert auch Optionen mit 'AutoReset'-Attribut
// value: Ggfs. zu setzender Wert
// return Promise auf Object mit den geladenen Optionen
// serial: Serialization fuer String-Werte (Select, Textarea)
async function invalidateOpts(optSet, force = false) {
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
     for (let opt in optSet) {
// return String mit dem (reinen) Funktionsaufruf
        const __OPT = optSet[opt];
function getFormAction(opt, isAlt = false, value = undefined, serial = undefined, memory = undefined) {
     const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;
    const __MEMSTR = __STORAGE.Display;
    const __RUNPREFIX = __STORAGE.Prefix;


    if (__MEMORY !== undefined) {
         await invalidateOpt(__OPT, force);
         const __RELOAD = "window.location.reload()";
        const __SETITEM = function(item, val, quotes = true) {
                              return (__MEMSTR + ".setItem('" + __RUNPREFIX + item + "', " + (quotes ? "'" + val + "'" : val) + "),");
                          };
        const __SETITEMS = function(cmd, key = undefined, val = undefined) {
                              return ('(' + __SETITEM('cmd', cmd) + ((key === undefined) ? "" :
                                      __SETITEM('key', key) + __SETITEM('val', val, false)) + __RELOAD + ')');
                          };
        const __CONFIG = getOptConfig(opt);
        const __SERIAL = getValue(serial, getValue(__CONFIG.Serial, false));
        const __THISVAL = ((__CONFIG.ValType === "String") ? "'\\x22' + this.value + '\\x22'" : "this.value");
        const __TVALUE = getValue(__CONFIG.ValType, __THISVAL, "new " + __CONFIG.ValType + '(' + __THISVAL + ')');
        const __VALSTR = ((value !== undefined) ? safeStringify(value) : __SERIAL ? "JSON.stringify(" + __TVALUE + ')' : __TVALUE);
        const __ACTION = (isAlt ? getValue(__CONFIG.AltAction, __CONFIG.Action) : __CONFIG.Action);
 
        if (__ACTION !== undefined) {
            switch (__ACTION) {
            case __OPTACTION.SET : //return "doActionSet('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                  return __SETITEMS('SET', getOptName(opt), __VALSTR);
            case __OPTACTION.NXT : //return "doActionNxt('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                  return __SETITEMS('NXT', getOptName(opt), __VALSTR);
            case __OPTACTION.RST : //return "doActionRst()";
                                  return __SETITEMS('RST');
            default :              break;
            }
        }
     }
     }


     return undefined;
     return optSet;
}
}


// Liefert die Funktionsaufruf zur Option als String
// Laedt eine (ueber Menu) gesetzte Option
// opt: Auszufuehrende Option
// opt: Zu ladende Option
// isAlt: Angabe, ob AltAction statt Action gemeint ist
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// value: Ggfs. zu setzender Wert
// return Promise auf gesetzten Wert der gelandenen Option
// type: Event-Typ fuer <input>, z.B. "click" fuer "onclick="
function loadOption(opt, force = false) {
// serial: Serialization fuer String-Werte (Select, Textarea)
    if (! opt.Promise) {
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
        const __CONFIG = getOptConfig(opt);
// return String mit dem (reinen) Funktionsaufruf
        const __ISSHARED = getValue(__CONFIG.Shared, false, true);
function getFormActionEvent(opt, isAlt = false, value = undefined, type = "click", serial = undefined, memory = undefined) {
        const __NAME = getOptName(opt);
    const __ACTION = getFormAction(opt, isAlt, value, serial, memory);
        const __DEFAULT = getOptValue(opt, undefined, false, false, false);
        let value;
 
        if (opt.Loaded && ! __ISSHARED) {
            const __ERROR = "Error: Oprion '" + __NAME + "' bereits geladen!";
 
            __LOG[0](__MESSAGE);
 
            return Promise.reject(__MESSAGE);
        }
 
        if (opt.ReadOnly || __ISSHARED) {
            value = __DEFAULT;
        } else if (! force && __CONFIG.AutoReset) {
            value = initOptValue(__CONFIG);
        } else {
            value = (__CONFIG.Serial ?
                            deserialize(__NAME, __DEFAULT) :
                            GM.getValue(__NAME, __DEFAULT));
        }
 
        opt.Promise = Promise.resolve(value).then(value => {
                // Paranoide Sicherheitsabfrage (das sollte nie passieren!)...
                if (opt.Loaded || ! opt.Promise) {
                    showAlert("Error", "Unerwarteter Widerspruch zwischen opt.Loaded und opt.Promise", safeStringify(opt));
                }
                __LOG[5]("LOAD " + __NAME + ": " + __LOG.changed(__DEFAULT, value));


    return getValue(__ACTION, "", ' on' + type + '="' + __ACTION + '"');
                // Wert intern setzen...
}
                const __VAL = setOptValue(opt, value);


// Zeigt eine Option auf der Seite als Auswahlbox an
                // Wert als geladen markieren...
// opt: Anzuzeigende Option
                opt.Promise = undefined;
// return String mit dem HTML-Code
                opt.Loaded = true;
function getOptionSelect(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt);
    const __ACTION = getFormActionEvent(opt, false, undefined, "change", undefined);
    const __FORMLABEL = getValue(__CONFIG.FormLabel, __CONFIG.Label);
    const __LABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
    let element = '<select name="' + __NAME + '" id="' + __NAME + '"' + __ACTION + '>';


    if (__CONFIG.FreeValue && ! (~ __CONFIG.Choice.indexOf(__VALUE))) {
                return __VAL;
        element += '\n<option value="' + __VALUE + '" SELECTED>' + __VALUE + '</option>';
            }, defaultCatch);
     }
     }
    for (let value of __CONFIG.Choice) {
        element += '\n<option value="' + value + '"' +
                  ((value === __VALUE) ? ' SELECTED' : "") +
                  '>' + value + '</option>';
    }
    element += '\n</select>';


     return __LABEL.replace('$', element);
     return opt.Promise;
}
}


// Zeigt eine Option auf der Seite als Radiobutton an
// Laedt die (ueber Menu) gesetzten Optionen
// opt: Anzuzeigende Option
// optSet: Object mit den Optionen
// return String mit dem HTML-Code
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
function getOptionRadio(opt) {
// return Array mit Promises neuer Ladevorgaenge (fuer Objekte mit 'name' und 'value')
     const __CONFIG = getOptConfig(opt);
function loadOptions(optSet, force = false) {
     const __NAME = getOptName(opt);
     const __PROMISES = [];
    const __VALUE = getOptValue(opt, false);
 
    const __ACTION = getFormActionEvent(opt, false, true, "click", false);
     for (let opt in optSet) {
    const __ALTACTION = getFormActionEvent(opt, true, false, "click", false);
        const __OPT = optSet[opt];
    const __ELEMENTON  = '<input type="radio" name="' + __NAME +
 
                        '" id="' + __NAME + 'ON" value="1"' +
        if (! __OPT.Loaded) {
                        (__VALUE ? ' CHECKED' : __ACTION) +
            const __PROMISE = loadOption(__OPT, force).then(value => {
                        ' /><label for="' + __NAME + 'ON">' +
                    __LOG[5]("LOADED " + opt + " << " + value);
                        __CONFIG.Label + '</label>';
 
    const __ELEMENTOFF = '<input type="radio" name="' + __NAME +
                    return Promise.resolve({
                        '" id="' + __NAME + 'OFF" value="0"' +
                            'name' : opt,
                        (__VALUE ? __ALTACTION : ' CHECKED') +
                            'value' : value
                        ' /><label for="' + __NAME + 'OFF">' +
                        });
                        __CONFIG.AltLabel + '</label>';
                }, defaultCatch);
 
            __PROMISES.push(__PROMISE);
        }
    }


     return [ __ELEMENTON, __ELEMENTOFF ];
     return Promise.all(__PROMISES);
}
}


// Zeigt eine Option auf der Seite als Checkbox an
// Entfernt eine (ueber Menu) gesetzte Option (falls nicht 'Permanent')
// opt: Anzuzeigende Option
// opt: Gesetzte Option
// return String mit dem HTML-Code
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
function getOptionCheckbox(opt) {
// reset: Setzt bei Erfolg auf Initialwert der Option (auch fuer nicht 'AutoReset')
// return Promise von GM.deleteValue() (oder void)
function deleteOption(opt, force = false, reset = true) {
     const __CONFIG = getOptConfig(opt);
     const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, "click", false);
    const __FORMLABEL = getValue(__CONFIG.FormLabel, __CONFIG.Label);


     return '<input type="checkbox" name="' + __NAME +
     if (force || ! __CONFIG.Permanent) {
          '" id="' + __NAME + '" value="' + __VALUE + '"' +
        const __NAME = getOptName(opt);
          (__VALUE ? ' CHECKED' : "") + __ACTION + ' /><label for="' +
 
          __NAME + '">' + __FORMLABEL + '</label>';
        __LOG[4]("DELETE " + __NAME);
 
        return GM.deleteValue(__NAME).then(voidValue => {
                if (reset || __CONFIG.AutoReset) {
                    setOptValue(opt, initOptValue(__CONFIG));
                }
            }, defaultCatch);
    }
 
    return Promise.resolve();
}
}


// Zeigt eine Option auf der Seite als Daten-Textfeld an
// Entfernt die (ueber Menu) gesetzten Optionen (falls nicht 'Permanent')
// opt: Anzuzeigende Option
// optSet: Gesetzte Optionen
// return String mit dem HTML-Code
// optSelect: Liste von ausgewaehlten Optionen, true = entfernen, false = nicht entfernen
function getOptionTextarea(opt) {
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
     const __CONFIG = getOptConfig(opt);
// reset: Setzt bei Erfolg auf Initialwert der Option
    const __NAME = getOptName(opt);
// return Promise auf diesen Vorgang
    const __VALUE = getOptValue(opt);
async function deleteOptions(optSet, optSelect = undefined, force = false, reset = true) {
    const __ACTION = getFormActionEvent(opt, false, undefined, "submit", undefined);
     const __DELETEALL = ((optSelect === undefined) || (optSelect === true));
     const __SUBMIT = getValue(__CONFIG.Submit, "");
     const __OPTSELECT = getValue(optSelect, { });
     //const __ONSUBMIT = (__SUBMIT.length ? ' onKeyDown="' + __SUBMIT + '"': "");
 
    const __ONSUBMIT = (__SUBMIT ? ' onKeyDown="' + __SUBMIT + '"': "");
     for (let opt in optSet) {
    const __FORMLABEL = getValue(__CONFIG.FormLabel, __CONFIG.Label);
        if (getValue(__OPTSELECT[opt], __DELETEALL)) {
    const __ELEMENTLABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
            await deleteOption(optSet[opt], force, reset);
    const __ELEMENTTEXT = '<textarea name="' + __NAME + '" id="' + __NAME + '" cols="' + __CONFIG.Cols +
        }
                          '" rows="' + __CONFIG.Rows + '"' + __ONSUBMIT + __ACTION + '>' +
    }
                          safeStringify(__VALUE, __CONFIG.Replace, __CONFIG.Space) + '</textarea>';


     return [ __ELEMENTLABEL, __ELEMENTTEXT ];
     return Promise.resolve();
}
}


// Zeigt eine Option auf der Seite als Button an
// Benennt eine Option um und laedt sie ggfs. nach
// opt: Anzuzeigende Option
// opt: Gesetzte Option
// return String mit dem HTML-Code
// name: Neu zu setzender Name (Speicheradresse)
function getOptionButton(opt) {
// reload: Wert nachladen statt beizubehalten
    const __CONFIG = getOptConfig(opt);
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Promise auf umbenannte Option
async function renameOption(opt, name, reload = false, force = false) {
     const __NAME = getOptName(opt);
     const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, "click", false);
    const __BUTTONLABEL = (__VALUE ? __CONFIG.AltLabel : __CONFIG.Label);
    const __FORMLABEL = getValue(__CONFIG.FormLabel, __CONFIG.Label);


     return '<label for="' + __NAME + '">' + __FORMLABEL +
     if (__NAME !== name) {
          '</label><input type="button" name="' + __NAME +
        await deleteOption(opt, true, ! reload);
          '" id="' + __NAME + '" value="' + __BUTTONLABEL + '"' +
 
          __ACTION + '/>';
        setOptName(opt, name);
}
 
        await invalidateOpt(opt, opt.Loaded);


// Zeigt eine Option auf der Seite an (je nach Typ)
        if (reload) {
// opt: Anzuzeigende Option
            opt.Loaded = false;
// return String mit dem HTML-Code
function getOptionElement(opt) {
    const __CONFIG = getOptConfig(opt);
    const __TYPE = getValue(__CONFIG.FormType, __CONFIG.Type);
    let element = "";


    if (! __CONFIG.Hidden) {
            await loadOption(opt, force);
        switch (__TYPE) {
        case __OPTTYPES.MC : element = getOptionSelect(opt);
                            break;
        case __OPTTYPES.SW : if (__CONFIG.FormLabel !== undefined) {
                                element = getOptionCheckbox(opt);
                            } else {
                                element = getOptionRadio(opt);
                            }
                            break;
        case __OPTTYPES.TF : element = getOptionCheckbox(opt);
                            break;
        case __OPTTYPES.SD : element = getOptionTextarea(opt);
                            break;
        case __OPTTYPES.SI : element = getOptionButton(opt);
                            break;
        default :            break;
         }
         }
    }


        if (element.length === 2) {
    return Promise.resolve(opt);
            element = '<div>' + element[0] + '<br />' + element[1] + '</div>';
}
        }
    }


     return element;
// Ermittelt einen neuen Namen mit einem Prefix. Parameter fuer renameOptions()
// name: Gesetzter Name (Speicheradresse)
// prefix: Prefix, das vorangestellt werden soll
// return Neu zu setzender Name (Speicheradresse)
function prefixName(name, prefix) {
     return (prefix + name);
}
}


// Baut das Benutzermenu auf der Seite auf
// Ermittelt einen neuen Namen mit einem Postfix. Parameter fuer renameOptions()
// optSet: Gesetzte Optionen
// name: Gesetzter Name (Speicheradresse)
// optParams: Eventuell notwendige Parameter
// postfix: Postfix, das angehaengt werden soll
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// return Neu zu setzender Name (Speicheradresse)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
function postfixName(name, postfix) {
// 'formWidth': Anzahl der Elemente pro Zeile
     return (name + postfix);
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return String mit dem HTML-Code
function getForm(optSet, optParams = { }) {
    const __FORM = '<form id="options" method="POST"><table><tbody><tr>';
    const __FORMEND = '</tr></tbody></table></form>';
    const __FORMWIDTH = getValue(optParams.formWidth, 3);
    const __FORMBREAK = getValue(optParams.formBreak, __FORMWIDTH);
    const __SHOWFORM = getOptValue(optSet.showForm, true) ? optParams.showForm : { 'showForm' : true };
     let form = __FORM;
    let count = 0;  // Bisher angezeigte Optionen
    let column = 0;  // Spalte der letzten Option (1-basierend)
 
    for (let opt in optSet) {
        if (checkItem(opt, __SHOWFORM, optParams.hideForm)) {
            const __ELEMENT = getOptionElement(optSet[opt]);
            const __TDOPT = (~ __ELEMENT.indexOf('|')) ? "" : ' colspan="2"';
 
            if (__ELEMENT) {
                if (++count > __FORMBREAK) {
                    if (++column > __FORMWIDTH) {
                        column = 1;
                    }
                }
                if (column === 1) {
                    form += '</tr><tr>';
                }
                form += '\n<td' + __TDOPT + '>' + __ELEMENT.replace('|', '</td><td>') + '</td>';
            }
        }
    }
    form += '\n' + __FORMEND;
 
    return form;
}
}


// Fuegt das Script in die Seite ein
// Benennt selektierte Optionen nach einem Schema um und laedt sie ggfs. nach
// optSet: Gesetzte Optionen
// optSet: Gesetzte Optionen
// optParams: Eventuell notwendige Parameter
// optSelect: Liste von ausgewaehlten Optionen, true = nachladen, false = nicht nachladen
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'reload': Option nachladen?
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'force': Option auch mit 'AutoReset'-Attribut nachladen?
// return String mit dem HTML-Code fuer das Script
// renameParam: Wird an renameFun uebergeen
function getScript(optSet, optParams = { }) {
// renameFun: function(name, param) zur Ermittlung des neuen Namens
    //const __SCRIPT = '<script type="text/javascript">function activateMenu() { console.log("TADAAA!"); }</script>';
// - name: Neu zu setzender Name (Speicheradresse)
    //const __SCRIPT = '<script type="text/javascript">\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionRst(key, value) { alert("RESET"); }\n</script>';
// - param: Parameter "renameParam" von oben, z.B. Prefix oder Postfix
    //const __FORM = '<form method="POST"><input type="button" id="showOpts" name="showOpts" value="Optionen anzeigen" onclick="activateMenu()" /></form>';
// return Promise auf diesen Vorgang
    const __SCRIPT = "";
async function renameOptions(optSet, optSelect, renameParam = undefined, renameFun = prefixName) {
    if (renameFun === undefined) {
        __LOG[0]("RENAME: Illegale Funktion!");
    }
    for (let opt in optSelect) {
        const __OPTPARAMS = optSelect[opt];
        const __OPT = optSet[opt];


    //window.eval('function activateMenu() { console.log("TADAAA!"); }');
        if (__OPT === undefined) {
            __LOG[0]("RENAME: Option '" + opt + "' nicht gefunden!");
        } else {
            const __NAME = getOptName(__OPT);
            const __NEWNAME = renameFun(__NAME, renameParam);
            const __ISSCALAR = ((typeof __OPTPARAMS) === 'boolean');
            // Laedt die unter dem neuen Namen gespeicherten Daten nach?
            const __RELOAD = (__ISSCALAR ? __OPTPARAMS : __OPTPARAMS.reload);
            // Laedt auch Optionen mit 'AutoReset'-Attribut?
            const __FORCE = (__ISSCALAR ? true : __OPTPARAMS.force);


     return __SCRIPT;
            await renameOption(__OPT, __NEWNAME, __RELOAD, __FORCE);
        }
     }
}
}


// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
// Setzt die Optionen in optSet auf die "Werkseinstellungen" des Skripts
// anchor: Element, das als Anker fuer die Anzeige dient
// optSet: Gesetzte Optionen
// optSet: Gesetzte Optionen
// optParams: Eventuell notwendige Parameter
// reload: Seite mit "Werkseinstellungen" neu laden
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// return Promise auf diesen Vorgang
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
async function resetOptions(optSet, reload = true) {
// 'formWidth': Anzahl der Elemente pro Zeile
    // Alle (nicht 'Permanent') gesetzten Optionen entfernen...
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
    await deleteOptions(optSet, true, false, ! reload);
function buildForm(anchor, optSet, optParams = { }) {
    __LOG[3]("buildForm()");


     const __FORM = getForm(optSet, optParams);
     // ... und ggfs. Seite neu laden (mit "Werkseinstellungen")...
     const __SCRIPT = getScript(optSet, optParams);
     refreshPage(reload);
 
    addForm(anchor, __FORM, __SCRIPT);
}
}


// Informationen zu hinzugefuegten Forms
// ==================== Abschnitt fuer diverse Utilities ====================
const __FORMS = { };


// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
// Legt Input-Felder in einem Form-Konstrukt an, falls noetig
// anchor: Element, das als Anker fuer die Anzeige dient
// form: <form>...</form>
// form: HTML-Form des Optionsmenu (hinten angefuegt)
// props: Map von name:value-Paaren
// script: Script mit Reaktionen
// type: Typ der Input-Felder (Default: unsichtbare Daten)
function addForm(anchor, form = "", script = "") {
// return Ergaenztes Form-Konstrukt
     const __OLDFORM = __FORMS[anchor];
function addInputField(form, props, type = 'hidden') {
    const __REST = (__OLDFORM === undefined) ? anchor.innerHTML :
     for (let fieldName in props) {
                  anchor.innerHTML.substring(0, anchor.innerHTML.length - __OLDFORM.Script.length - __OLDFORM.Form.length);
        let field = form[fieldName];
        if (! field) {
            field = document.createElement('input');
            field.type = type;
            field.name = fieldName;
            form.appendChild(field);
        }
        field.value = props[fieldName];
    }


     __FORMS[anchor] = {
     return form;
                          'Script' : script,
                          'Form'  : form
                      };
 
    anchor.innerHTML = __REST + script + form;
}
}


// ==================== Abschnitt fuer Klasse Classification ====================
// Legt unsichtbare Input-Daten in einem Form-Konstrukt an, falls noetig
 
// form: <form>...</form>
// Basisklasse fuer eine Klassifikation der Optionen nach Kriterium (z.B. Erst- und Zweitteam oder Fremdteam)
// props: Map von name:value-Paaren
function Classification() {
// return Ergaenztes Form-Konstrukt
     'use strict';
function addHiddenField(form, props) {
 
     return addInputField(form, props, 'hidden');
    this.renameFun = prefixName;
    //this.renameParamFun = undefined;
    this.optSet = undefined;
    this.optSelect = { };
}
}


Class.define(Classification, Object, {
// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
                    'renameOptions' : function() {
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
                                          const __PARAM = this.renameParamFun();
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function addEvent(obj, type, callback, capture = false) {
    if (obj.addEventListener) {
        return obj.addEventListener(type, callback, capture);
    } else if (obj.attachEvent) {
        return obj.attachEvent('on' + type, callback);
    } else {
        __LOG[0]("Could not add " + type + " event:");
        __LOG[2](callback);


                                          if (__PARAM !== undefined) {
        return false;
                                              // Klassifizierte Optionen umbenennen...
    }
                                              renameOptions(this.optSet, this.optSelect, __PARAM, this.renameFun);
}
                                          }
                                      },
                    'deleteOptions' : function() {
                                          return deleteOptions(this.optSet, this.optSelect, true, true);
                                      }
                } );


// ==================== Ende Abschnitt fuer Klasse Classification ====================
// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function removeEvent(obj, type, callback, capture = false) {
    if (obj.removeEventListener) {
        return obj.removeEventListener(type, callback, capture);
    } else if (obj.detachEvent) {
        return obj.detachEvent('on' + type, callback);
    } else {
        __LOG[0]("Could not remove " + type + " event:");
        __LOG[2](callback);


// ==================== Abschnitt fuer Klasse TeamClassification ====================
        return false;
    }
}


// Klasse fuer die Klassifikation der Optionen nach Team (Erst- und Zweitteam oder Fremdteam)
// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
function TeamClassification() {
// id: ID des betroffenen Eingabeelements
     'use strict';
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function addDocEvent(id, type, callback, capture = false) {
     const __OBJ = document.getElementById(id);


     Classification.call(this);
     return addEvent(__OBJ, type, callback, capture);
 
    this.team = undefined;
    this.teamParams = undefined;
}
}


Class.define(TeamClassification, Classification, {
// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
                    'renameParamFun' : function() {
// id: ID des betroffenen Eingabeelements
                                          const __MYTEAM = (this.team = getMyTeam(this.optSet, this.teamParams, this.team));
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function removeDocEvent(id, type, callback, capture = false) {
    const __OBJ = document.getElementById(id);


                                          if (__MYTEAM.LdNr) {
    return removeEvent(__OBJ, type, callback, capture);
                                              // Prefix fuer die Optionen mit gesonderten Behandlung...
}
                                              return __MYTEAM.LdNr.toString() + '.' + __MYTEAM.LgNr.toString() + ':';
                                          } else {
                                              return undefined;
                                          }
                                      }
                } );


// ==================== Ende Abschnitt fuer Klasse TeamClassification ====================
// Hilfsfunktion fuer die Ermittlung eines Elements der Seite
// name: Name des Elements (siehe "name=")
// index: Laufende Nummer des Elements (0-based), Default: 0
// doc: Dokument (document)
// return Gesuchtes Element mit der lfd. Nummer index oder undefined (falls nicht gefunden)
function getElement(name, index = 0, doc = document) {
    const __TAGS = doc.getElementsByName(name);
    const __TABLE = (__TAGS ? __TAGS[index] : undefined);


// ==================== Abschnitt fuer Klasse Team ====================
    return __TABLE;
}


// Klasse fuer Teamdaten
// Hilfsfunktion fuer die Ermittlung eines Elements der Seite (Default: Tabelle)
function Team(team, land, liga) {
// index: Laufende Nummer des Elements (0-based)
     'use strict';
// tag: Tag des Elements ("table")
// doc: Dokument (document)
// return Gesuchtes Element oder undefined (falls nicht gefunden)
function getTable(index, tag = 'table', doc = document) {
     const __TAGS = doc.getElementsByTagName(tag);
    const __TABLE = (__TAGS ? __TAGS[index] : undefined);


     this.Team = team;
     return __TABLE;
    this.Land = land;
    this.Liga = liga;
    this.LdNr = getLandNr(land);
    this.LgNr = getLigaNr(liga);
}
}


Class.define(Team, Object, {
// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
                    '__TEAMITEMS' : {  // Items, die in Team als Teamdaten gesetzt werden...
// name: Name des Tabellen-Elements (siehe "name=")
                                        'Team' : true,
// index: Laufende Nummer des Tabellen-Elements (0-based), Default: 0
                                        'Liga' : true,
// doc: Dokument (document)
                                        'Land' : true,
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
                                        'LdNr' : true,
function getElementRows(name, index = 0, doc = document) {
                                        'LgNr' : true
    const __TABLE = getElement(name, index, doc);
                                    }
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);
                } );


// ==================== Ende Abschnitt fuer Klasse Team ====================
    return __ROWS;
}


// ==================== Spezialisierter Abschnitt fuer Optionen ====================
// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
// index: Laufende Nummer des Elements (0-based)
// doc: Dokument (document)
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
function getRows(index, doc = document) {
    const __TABLE = getTable(index, 'table', doc);
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);


// Gesetzte Optionen (wird von initOptions() angelegt und von loadOptions() gefuellt):
    return __ROWS;
const __OPTSET = { };
}


// Teamparameter fuer getrennte Speicherung der Optionen fuer Erst- und Zweitteam...
// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
const __TEAMCLASS = new TeamClassification();
// id: ID des Tabellen-Elements
// doc: Dokument (document)
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
function getRowsById(id, doc = document) {
    const __TABLE = doc.getElementById(id);
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);


// Optionen mit Daten, die ZAT- und Team-bezogen gemerkt werden...
     return __ROWS;
__TEAMCLASS.optSelect = {
}
                      'datenZat'  : true,
                      'birthdays'  : true,
                      'tClasses'  : true,
                      'progresses' : true,
                      'zatAges'    : true,
                      'trainiert'  : true,
                      'positions'  : true,
                      'skills'     : true
                  };


// Gibt die Teamdaten zurueck und aktualisiert sie ggfs. in der Option
// ==================== Abschnitt fuer Optionen auf der Seite ====================
// optSet: Platz fuer die gesetzten Optionen
// teamParams: Dynamisch ermittelte Teamdaten ('Team', 'Liga', 'Land', 'LdNr' und 'LgNr')
// myTeam: Objekt fuer die Teamdaten
// return Die Teamdaten oder undefined bei Fehler
function getMyTeam(optSet = undefined, teamParams = undefined, myTeam = new Team()) {
    if (teamParams !== undefined) {
        addProps(myTeam, teamParams, myTeam.__TEAMITEMS);
        __LOG[2]("Ermittelt: " + safeStringify(myTeam));
        // ... und abspeichern...
        setOpt(optSet.team, myTeam, false);
    } else {
        const __TEAM = getOptValue(optSet.team);  // Gespeicherte Parameter


        if ((__TEAM !== undefined) && (__TEAM.Land !== undefined)) {
// Liefert den Funktionsaufruf zur Option als String
            addProps(myTeam, __TEAM, myTeam.__TEAMITEMS);
// opt: Auszufuehrende Option
            __LOG[2]("Gespeichert: " + safeStringify(myTeam));
// isAlt: Angabe, ob AltAction statt Action gemeint ist
        } else {
// value: Ggfs. zu setzender Wert
            __LOG[1]("Unbekannt: " + safeStringify(__TEAM));
// serial: Serialization fuer String-Werte (Select, Textarea)
        }
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
     }
// return String mit dem (reinen) Funktionsaufruf
function getFormAction(opt, isAlt = false, value = undefined, serial = undefined, memory = undefined) {
    const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;
    const __MEMSTR = __STORAGE.Display;
     const __RUNPREFIX = __STORAGE.Prefix;


     return myTeam;
     if (__MEMORY !== undefined) {
}
        const __RELOAD = "window.location.reload()";
 
        const __SETITEM = function(item, val, quotes = true) {
// Behandelt die Optionen und laedt das Benutzermenu
                              return (__MEMSTR + ".setItem('" + __RUNPREFIX + item + "', " + (quotes ? "'" + val + "'" : val) + "),");
// optConfig: Konfiguration der Optionen
                          };
// optSet: Platz fuer die gesetzten Optionen
        const __SETITEMS = function(cmd, key = undefined, val = undefined) {
// optParams: Eventuell notwendige Parameter zur Initialisierung
                              return ('(' + __SETITEM('cmd', cmd) + ((key === undefined) ? "" :
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
                                      __SETITEM('key', key) + __SETITEM('val', val, false)) + __RELOAD + ')');
// 'teamParams': Getrennte Daten-Option wird genutzt, hier: Team() mit 'LdNr'/'LgNr' des Erst- bzw. Zweitteams
                          };
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
        const __CONFIG = getOptConfig(opt);
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
        const __SERIAL = getValue(serial, getValue(__CONFIG.Serial, false));
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
        const __THISVAL = ((__CONFIG.ValType === 'String') ? "'\\x22' + this.value + '\\x22'" : "this.value");
// 'formWidth': Anzahl der Elemente pro Zeile
        const __TVALUE = getValue(__CONFIG.ValType, __THISVAL, "new " + __CONFIG.ValType + '(' + __THISVAL + ')');
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
        const __VALSTR = ((value !== undefined) ? safeStringify(value) : __SERIAL ? "JSON.stringify(" + __TVALUE + ')' : __TVALUE);
// return Gefuelltes Objekt mit den gesetzten Optionen
        const __ACTION = (isAlt ? getValue(__CONFIG.AltAction, __CONFIG.Action) : __CONFIG.Action);
function buildOptions(optConfig, optSet = undefined, optParams = { 'hideMenu' : false }) {
    // Klassifikation ueber Land und Liga des Teams...
    __TEAMCLASS.optSet = optSet;  // Classification mit optSet verknuepfen
    __TEAMCLASS.teamParams = optParams.teamParams; // Ermittelte Parameter


    optSet = startOptions(optConfig, optSet, __TEAMCLASS);
        if (__ACTION !== undefined) {
            switch (__ACTION) {
            case __OPTACTION.SET : //return "doActionSet('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                  return __SETITEMS('SET', getOptName(opt), __VALSTR);
            case __OPTACTION.NXT : //return "doActionNxt('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                  return __SETITEMS('NXT', getOptName(opt), __VALSTR);
            case __OPTACTION.RST : //return "doActionRst()";
                                  return __SETITEMS('RST');
            default :              break;
            }
        }
    }


    showOptions(optSet, optParams);
     return undefined;
 
     return optSet;
}
}


// ==================== Ende Abschnitt fuer Optionen ====================
// Liefert die Funktionsaufruf zur Option als String
 
// opt: Auszufuehrende Option
// ==================== Abschnitt genereller Code zur Anzeige der Jugend ====================
// isAlt: Angabe, ob AltAction statt Action gemeint ist
 
// value: Ggfs. zu setzender Wert
// Funktionen ***************************************************************************
// type: Event-Typ fuer <input>, z.B. "click" fuer "onclick="
// serial: Serialization fuer String-Werte (Select, Textarea)
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return String mit dem (reinen) Funktionsaufruf
function getFormActionEvent(opt, isAlt = false, value = undefined, type = 'click', serial = undefined, memory = undefined) {
    const __ACTION = getFormAction(opt, isAlt, value, serial, memory);


// Erschafft die Spieler-Objekte und fuellt sie mit Werten
     return getValue(__ACTION, "", ' on' + type + '="' + __ACTION + '"');
// reloadData: true = Teamuebersicht, false = Spielereinzelwerte
}
function init(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, reloadData = false) {
     storePlayerDataFromHTML(playerRows, optSet, colIdx, offsetUpper, offsetLower, reloadData);
 
    const __SAISON = getOptValue(optSet.saison);
    const __CURRZAT = getOptValue(optSet.aktuellerZat);
    const __BIRTHDAYS = getOptValue(optSet.birthdays, []);
    const __TCLASSES = getOptValue(optSet.tClasses, []);
    const __PROGRESSES = getOptValue(optSet.progresses, []);
    const __ZATAGES = getOptValue(optSet.zatAges, []);
    const __TRAINIERT = getOptValue(optSet.trainiert, []);
    const __POSITIONS = getOptValue(optSet.positions, []);
    const __SKILLS = getOptValue(optSet.skills, []);
    const __PLAYERS = [];


    for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++, j++) {
// Hilfsfunktion: Wendet eine Konvertierung auf jede "Zeile" innerhalb eines Textes an
        const __CELLS = playerRows[i].cells;
// text: Urspruenglicher Text
        const __AGE = getIntFromHTML(__CELLS, colIdx.Age);
// convFun: function(line, index, arr): Konvertiert line in "Zeile" line des Arrays arr
        const __ISGOALIE = isGoalieFromHTML(__CELLS, colIdx.Age);
// separator: Zeilentrenner im Text (Default: '\n')
        const __NEWPLAYER = new PlayerRecord(__AGE, getValue(__SKILLS[j], []), __ISGOALIE);
// thisArg: optionaler this-Parameter fuer die Konvertierung
// limit: optionale Begrenzung der Zeilen
// return String mit dem neuen Text
function eachLine(text, convFun, separator = '\n', thisArg = undefined, limit = undefined) {
    const __ARR = text.split(separator, limit);
    const __RES = __ARR.map(convFun, thisArg);


        __NEWPLAYER.initPlayer(__SAISON, __CURRZAT, __BIRTHDAYS[j], __TCLASSES[j], __PROGRESSES[j]);
    return __RES.join(separator);
}


        if (reloadData) {
// Hilfsfunktion: Ergaenzt einen HTML-Code um einen Titel (ToolTip)
            __NEWPLAYER.setZusatz(__ZATAGES[j], __TRAINIERT[j], __POSITIONS[j]);
// html: Urspruenglicher HTML-Code (z.B. ein HTML-Element oder Text)
        }
// title: Im ToolTip angezeigter Text
 
// separator: Zeilentrenner im Text (Default: '|')
        __PLAYERS[j] = __NEWPLAYER;
// limit: optionale Begrenzung der Zeilen
    }
// return String mit dem neuen HTML-Code
 
function withTitle(html, title, separator = '|', limit = undefined) {
     if (! reloadData) {
     if (title && title.length) {
         calcPlayerData(__PLAYERS, optSet);
         return eachLine(html, line => '<abbr title="' + title + '">' + line + '</abbr>', separator, undefined, limit);
    } else {
        return html;
     }
     }
    return __PLAYERS;
}
}


// Berechnet die abgeleiteten Werte in den Spieler-Objekten neu und speichert diese
// Hilfsfunktion: Ermittelt einen Label- oder FormLabel-Eintrag (Default)
function calcPlayerData(players, optSet) {
// label: Config-Eintrag fuer Label oder FormLabel
    const __ZATAGES = [];
// defLabel: Ersatzwert, falls label nicht angegeben
    const __TRAINIERT = [];
// isSelect: Angabe, ob ein Parameter angezeigt wird (Default: false)
     const __POSITIONS = [];
// isForm: Angabe, ob ein FormLabel gesucht ist (Default: true)
// return Vollstaendiger Label- oder FormLabel-Eintrag
function formatLabel(label, defLabel = undefined, isSelect = false, isForm = true) {
     const __LABEL = getValue(label, defLabel);


     for (let i = 0; i < players.length; i++) {
     if (isSelect && __LABEL && (substParam(__LABEL, '_') === __LABEL)) {
         const __ZUSATZ = players[i].calcZusatz();
         return __LABEL + (isForm ? "|$" : " $");
 
    } else {
        if (__ZUSATZ.zatAge !== undefined) { // braucht Geburtstag fuer gueltige Werte!
         return __LABEL;
            __ZATAGES[i]    = __ZUSATZ.zatAge;
        }
        __TRAINIERT[i]  = __ZUSATZ.trainiert;
         __POSITIONS[i]  = __ZUSATZ.bestPos;
     }
     }
    setOpt(optSet.zatAges, __ZATAGES, false);
    setOpt(optSet.trainiert, __TRAINIERT, false);
    setOpt(optSet.positions, __POSITIONS, false);
}
}


// Ermittelt die Werte in den Spieler-Objekten aus den Daten der Seite und speichert diese
// Zeigt eine Option auf der Seite als Auswahlbox an
// reloadData: true = Teamuebersicht, false = Spielereinzelwerte
// opt: Anzuzeigende Option
function storePlayerDataFromHTML(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, reloadData = false) {
// return String mit dem HTML-Code
     if (reloadData) {
function getOptionSelect(opt) {
        const __BIRTHDAYS = [];
    const __CONFIG = getOptConfig(opt);
        const __TCLASSES = [];
    const __NAME = getOptName(opt);
        const __PROGRESSES = [];
    const __VALUE = getOptValue(opt);
    const __ACTION = getFormActionEvent(opt, false, undefined, 'change', undefined);
     const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label, true);
    const __TITLE = substParam(getValue(__CONFIG.Title, __CONFIG.Label), __VALUE);
    const __LABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
    let element = '<select name="' + __NAME + '" id="' + __NAME + '"' + __ACTION + '>';


        for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++, j++) {
    if (__CONFIG.FreeValue && ! (~ __CONFIG.Choice.indexOf(__VALUE))) {
            const __CELLS = playerRows[i].cells;
         element += '\n<option value="' + __VALUE + '" SELECTED>' + __VALUE + '</option>';
 
            __BIRTHDAYS[j] = getIntFromHTML(__CELLS, colIdx.Geb);
            __TCLASSES[j] = getTalentFromHTML(__CELLS, colIdx.Tal);
            __PROGRESSES[j] = getAufwertFromHTML(__CELLS, colIdx.Auf, getOptValue(optSet.shortAufw, true));
        }
        setOpt(optSet.birthdays, __BIRTHDAYS, false);
        setOpt(optSet.tClasses, __TCLASSES, false);
        setOpt(optSet.progresses, __PROGRESSES, false);
    } else {
         const __SKILLS = [];
 
        for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++, j++) {
            const __CELLS = playerRows[i].cells;
 
            __SKILLS[j] = getSkillsFromHTML(__CELLS, colIdx);
        }
        setOpt(optSet.skills, __SKILLS, false);
     }
     }
}
    for (let value of __CONFIG.Choice) {
 
        element += '\n<option value="' + value + '"' +
// Trennt die Gruppen (z.B. Jahrgaenge) mit Linien
                  ((value === __VALUE) ? ' SELECTED' : "") +
function separateGroups(rows, borderString, colIdxSort = 0, offsetUpper = 1, offsetLower = 0, offsetLeft = -1, offsetRight = 0, formatFun = sameValue) {
                  '>' + value + '</option>';
    if (offsetLeft < 0) {
        offsetLeft = colIdxSort; // ab Sortierspalte
     }
     }
    element += '\n</select>';


     for (let i = offsetUpper, newVal, oldVal = formatFun(rows[i].cells[colIdxSort].textContent); i < rows.length - offsetLower - 1; i++, oldVal = newVal) {
     return withTitle(substParam(__LABEL, element), __TITLE);
        newVal = formatFun(rows[i + 1].cells[colIdxSort].textContent);
        if (newVal !== oldVal) {
            for (let j = offsetLeft; j < rows[i].cells.length - offsetRight; j++) {
                rows[i].cells[j].style.borderBottom = borderString;
            }
        }
    }
}
}


// Klasse ColumnManager *****************************************************************
// Zeigt eine Option auf der Seite als Radiobutton an
 
// opt: Anzuzeigende Option
function ColumnManager(optSet, colIdx, showCol) {
// return String mit dem HTML-Code
     'use strict';
function getOptionRadio(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, false, true, 'click', false);
    const __ALTACTION = getFormActionEvent(opt, true, false, 'click', false);
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel); // nur nutzen, falls angegeben
    const __TITLE = getValue(__CONFIG.Title, '$');
    const __TITLEON = substParam(__TITLE, __CONFIG.Label);
    const __TITLEOFF = substParam(getValue(__CONFIG.AltTitle, __TITLE), __CONFIG.AltLabel);
    const __ELEMENTON  = '<input type="radio" name="' + __NAME +
                        '" id="' + __NAME + 'ON" value="1"' +
                        (__VALUE ? ' CHECKED' : __ACTION) +
                        ' /><label for="' + __NAME + 'ON">' +
                        __CONFIG.Label + '</label>';
     const __ELEMENTOFF = '<input type="radio" name="' + __NAME +
                        '" id="' + __NAME + 'OFF" value="0"' +
                        (__VALUE ? __ALTACTION : ' CHECKED') +
                        ' /><label for="' + __NAME + 'OFF">' +
                        __CONFIG.AltLabel + '</label>';
    const __ELEMENT = [
                          withTitle(__FORMLABEL, __VALUE ? __TITLEON : __TITLEOFF),
                          withTitle(__ELEMENTON, __TITLEON),
                          withTitle(__ELEMENTOFF, __TITLEOFF)
                      ];


     __LOG[3]("ColumnManager()");
     return ((__FORMLABEL && __FORMLABEL.length) ? __ELEMENT : __ELEMENT.slice(1, 3));
}


     const __SHOWCOL = getValue(showCol, true);
// Zeigt eine Option auf der Seite als Checkbox an
     const __SHOWALL = ((__SHOWCOL === true) || (__SHOWCOL.Default === true));
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionCheckbox(opt) {
     const __CONFIG = getOptConfig(opt);
     const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, 'click', false);
    const __VALUELABEL = (__VALUE ? __CONFIG.Label : getValue(__CONFIG.AltLabel, __CONFIG.Label));
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label);
    const __TITLE = substParam(getValue(__VALUE ? __CONFIG.Title : getValue(__CONFIG.AltTitle, __CONFIG.Title), '$'), __VALUELABEL);


     const __BIRTHDAYS = getOptValue(optSet.birthdays, []).length;
     return withTitle('<input type="checkbox" name="' + __NAME +
    const __TCLASSES = getOptValue(optSet.tClasses, []).length;
                    '" id="' + __NAME + '" value="' + __VALUE + '"' +
    const __PROGRESSES = getOptValue(optSet.progresses, []).length;
                    (__VALUE ? ' CHECKED' : "") + __ACTION + ' /><label for="' +
                    __NAME + '">' + __FORMLABEL + '</label>', __TITLE);
}


     const __ZATAGES = getOptValue(optSet.zatAges, []).length;
// Zeigt eine Option auf der Seite als Daten-Textfeld an
     const __TRAINIERT = getOptValue(optSet.trainiert, []).length;
// opt: Anzuzeigende Option
     const __POSITIONS = getOptValue(optSet.positions, []).length;
// return String mit dem HTML-Code
 
function getOptionTextarea(opt) {
     const __EINZELSKILLS = getOptValue(optSet.skills, []).length;
    const __CONFIG = getOptConfig(opt);
     const __PROJECTION = (__EINZELSKILLS && __ZATAGES);
    const __NAME = getOptName(opt);
 
     const __VALUE = getOptValue(opt);
     this.colIdx = colIdx;
     const __ACTION = getFormActionEvent(opt, false, undefined, 'submit', undefined);
     const __SUBMIT = getValue(__CONFIG.Submit, "");
    //const __ONSUBMIT = (__SUBMIT.length ? ' onKeyDown="' + __SUBMIT + '"': "");
    const __ONSUBMIT = (__SUBMIT ? ' onKeyDown="' + __SUBMIT + '"': "");
     const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label);
     const __TITLE = substParam(getValue(__CONFIG.Title, '$'), __FORMLABEL);
    const __ELEMENTLABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
     const __ELEMENTTEXT = '<textarea name="' + __NAME + '" id="' + __NAME + '" cols="' + __CONFIG.Cols +
                          '" rows="' + __CONFIG.Rows + '"' + __ONSUBMIT + __ACTION + '>' +
                          safeStringify(__VALUE, __CONFIG.Replace, __CONFIG.Space) + '</textarea>';


     this.geb = (__BIRTHDAYS && getValue(__SHOWCOL.zeigeGeb, __SHOWALL) && getOptValue(optSet.zeigeGeb));
     return [ withTitle(__ELEMENTLABEL, __TITLE), __ELEMENTTEXT ];
    this.tal = (__TCLASSES && getValue(__SHOWCOL.zeigeTal, __SHOWALL) && getOptValue(optSet.zeigeTal));
    this.quo = (__ZATAGES && __TRAINIERT && getValue(__SHOWCOL.zeigeQuote, __SHOWALL) && getOptValue(optSet.zeigeQuote));
    this.aufw = (__PROGRESSES && getValue(__SHOWCOL.zeigeAufw, __SHOWALL) && getOptValue(optSet.zeigeAufw));
    this.substAge = (__ZATAGES && getValue(__SHOWCOL.ersetzeAlter, __SHOWALL) && getOptValue(optSet.ersetzeAlter));
    this.alter = (__ZATAGES && getValue(__SHOWCOL.zeigeAlter, __SHOWALL) && getOptValue(optSet.zeigeAlter));
    this.fix = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeFixSkills, __SHOWALL) && getOptValue(optSet.zeigeFixSkills));
    this.tr = (__EINZELSKILLS && __TRAINIERT && getValue(__SHOWCOL.zeigeTrainiert, __SHOWALL) && getOptValue(optSet.zeigeTrainiert));
    this.antHpt = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeAnteilPri, __SHOWALL) && getOptValue(optSet.zeigeAnteilPri));
    this.antNeb = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeAnteilSec, __SHOWALL) && getOptValue(optSet.zeigeAnteilSec));
    this.pri = (__EINZELSKILLS && getValue(__SHOWCOL.zeigePrios, __SHOWALL) && getOptValue(optSet.zeigePrios));
    this.skill = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeSkill, __SHOWALL) && getOptValue(optSet.zeigeSkill));
    this.pos = (__EINZELSKILLS && __POSITIONS && getValue(__SHOWCOL.zeigePosition, __SHOWALL) && getOptValue(optSet.zeigePosition));
    this.anzOpti = ((__EINZELSKILLS && getValue(__SHOWCOL.zeigeOpti, __SHOWALL)) ? getOptValue(optSet.anzahlOpti) : 0);
    this.anzMw =  ((__PROJECTION && getValue(__SHOWCOL.zeigeMW, __SHOWALL)) ? getOptValue(optSet.anzahlMW) : 0);
    this.trE = (__PROJECTION && __TRAINIERT && getValue(__SHOWCOL.zeigeTrainiertEnde, __SHOWALL) && getOptValue(optSet.zeigeTrainiertEnde));
    this.antHptE = (__PROJECTION && getValue(__SHOWCOL.zeigeAnteilPriEnde, __SHOWALL) && getOptValue(optSet.zeigeAnteilPriEnde));
    this.antNebE = (__PROJECTION && getValue(__SHOWCOL.zeigeAnteilSecEnde, __SHOWALL) && getOptValue(optSet.zeigeAnteilSecEnde));
    this.priE = (__PROJECTION && getValue(__SHOWCOL.zeigePriosEnde, __SHOWALL) && getOptValue(optSet.zeigePriosEnde));
    this.skillE = (__PROJECTION && getValue(__SHOWCOL.zeigeSkillEnde, __SHOWALL) && getOptValue(optSet.zeigeSkillEnde));
    this.anzOptiE = ((__PROJECTION && getValue(__SHOWCOL.zeigeOptiEnde, __SHOWALL)) ? getOptValue(optSet.anzahlOptiEnde) : 0);
    this.anzMwE = ((__PROJECTION && getValue(__SHOWCOL.zeigeMWEnde, __SHOWALL)) ? getOptValue(optSet.anzahlMWEnde) : 0);
    this.kennzE = getOptValue(optSet.kennzeichenEnde);
}
}


Class.define(ColumnManager, Object, {
// Zeigt eine Option auf der Seite als Button an
        'toString'      : function() {
// opt: Anzuzeigende Option
                              let result = "Skillschnitt\t\t" + this.skill + '\n';
// return String mit dem HTML-Code
                              result += "Beste Position\t" + this.pos + '\n';
function getOptionButton(opt) {
                              result += "Optis\t\t\t" + this.anzOpti + '\n';
    const __CONFIG = getOptConfig(opt);
                              result += "Marktwerte\t\t" + this.anzMw + '\n';
    const __NAME = getOptName(opt);
                              result += "Skillschnitt Ende\t" + this.skillE + '\n';
    const __VALUE = getOptValue(opt, false);
                              result += "Optis Ende\t\t" + this.anzOptiE + '\n';
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, 'click', false);
                              result += "Marktwerte Ende\t" + this.anzMwE + '\n';
    const __BUTTONLABEL = (__VALUE ? getValue(__CONFIG.AltLabel, __CONFIG.Label) : __CONFIG.Label);
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __BUTTONLABEL);
    const __BUTTONTITLE = substParam(getValue(__VALUE ? getValue(__CONFIG.AltTitle, __CONFIG.Title) : __CONFIG.Title, '$'), __BUTTONLABEL);


                              return result;
    return '<label for="' + __NAME + '">' + __FORMLABEL + '</label>' +
                          },
          withTitle('<input type="button" name="" + ' + __NAME +
        'addCell'       : function(tableRow) {
                    '" id="' + __NAME + '" value="' + __BUTTONLABEL +
                              tableRow.insertCell(-1);
                    '"' + __ACTION + '/>', __BUTTONTITLE);
                              return tableRow.cells.length - 1;
}
                          },
        'addAndFillCell' : function(tableRow, value, color, digits = 2) {
                              let text = value;


                              if (value && isFinite(value) && (value !== true) && (value !== false)) {
// Zeigt eine Option auf der Seite an (je nach Typ)
                                  // Zahl einfuegen
// opt: Anzuzeigende Option
                                  if (value < 1000) {
// return String mit dem HTML-Code
                                      // Mit Nachkommastellen darstellen
function getOptionElement(opt) {
                                      text = parseFloat(value).toFixed(digits);
    const __CONFIG = getOptConfig(opt);
                                  } else {
    const __TYPE = getValue(__CONFIG.FormType, __CONFIG.Type);
                                      // Mit Tausenderpunkten darstellen
    let element = "";
                                      text = getNumberString(value.toString());
                                  }
                              }


                              // String, Boolean oder Zahl einfuegen...
    if (! __CONFIG.Hidden) {
                              tableRow.cells[this.addCell(tableRow)].textContent = text;
        switch (__TYPE) {
                              tableRow.cells[tableRow.cells.length - 1].style.color = color;
        case __OPTTYPES.MC : element = getOptionSelect(opt);
                          },
                            break;
         'addTitles'     : function(headers, titleColor = "#FFFFFF") {
        case __OPTTYPES.SW : if (__CONFIG.FormLabel !== undefined) {
                              // Spaltentitel zentrieren
                                element = getOptionCheckbox(opt);
                              headers.align = "center";
                            } else {
 
                                element = getOptionRadio(opt);
                              // Titel fuer die aktuellen Werte
                            }
                              if (this.tal) {
                            break;
                                  this.addAndFillCell(headers, "Talent", titleColor);
        case __OPTTYPES.TF : element = getOptionCheckbox(opt);
                              }
                            break;
                              if (this.quo) {
        case __OPTTYPES.SD : element = getOptionTextarea(opt);
                                  this.addAndFillCell(headers, "Quote", titleColor);
                            break;
                              }
        case __OPTTYPES.SI : element = getOptionButton(opt);
                              if (this.aufw) {
                            break;
                                  this.addAndFillCell(headers, "Aufwertung", titleColor);
        default :            break;
                              }
        }
                              if (this.geb) {
 
                                  this.addAndFillCell(headers, "Geb.", titleColor);
         if ((typeof element) !== 'string') {
                              }
            element = '<div>' + Array.from(element).join('<br />') + '</div>';
                              if (this.alter && ! this.substAge) {
        }
                                  this.addAndFillCell(headers, "Alter", titleColor);
    }
                              }
 
                              if (this.fix) {
    return element;
                                  this.addAndFillCell(headers, "fix", titleColor);
}
                              }
 
                              if (this.tr) {
// Gruppiert die Daten eines Objects nach einem Kriterium
                                  this.addAndFillCell(headers, "tr.", titleColor);
// data: Object mit Daten
                              }
// byFun: function(val), die das Kriterium ermittelt. Default: value
                              if (this.antHpt) {
// filterFun: function(key, index, arr), die das Kriterium key im Array arr an der Stelle index vergleicht. Default: Wert identisch
                                  this.addAndFillCell(headers, "%H", titleColor);
// sortFun: function(a, b), nach der die Kriterien sortiert werden. Default: Array.sort()
                              }
// return Neues Object mit Eintraegen der Form <Kriterium> : [ <alle Keys zu diesem Kriterium> ]
                              if (this.antNeb) {
function groupData(data, byFun, filterFun, sortFun) {
                                  this.addAndFillCell(headers, "%N", titleColor);
    const __BYFUN = (byFun || (val => val));
                              }
    const __FILTERFUN = (filterFun || ((key, index, arr) => (arr[index] === key)));
                              if (this.pri) {
    const __KEYS = Object.keys(data);
                                  this.addAndFillCell(headers, "Prios", titleColor);
    const __VALS = Object.values(data);
                              }
    const __BYKEYS = __VALS.map(__BYFUN);
                              if (this.skill) {
    const __BYKEYSET = new Set(__BYKEYS);
                                  this.addAndFillCell(headers, "Skill", titleColor);
    const __BYKEYARRAY = [...__BYKEYSET];
                              }
    const __SORTEDKEYS = __BYKEYARRAY.sort(sortFun);
                              if (this.pos) {
    const __GROUPEDKEYS = __SORTEDKEYS.map(byVal => __KEYS.filter((key, index, arr) => __FILTERFUN(byVal, index, __BYKEYS)));
                                  this.addAndFillCell(headers, "Pos", titleColor);
    const __ASSIGN = ((keyArr, valArr) => Object.assign({ }, ...keyArr.map((key, index) => ({ [key] : valArr[index] }))));
                              }
 
                              for (let i = 1; i <= 6; i++) {
    return __ASSIGN(__SORTEDKEYS, __GROUPEDKEYS);
                                  if (i <= this.anzOpti) {
}
                                      this.addAndFillCell(headers, "Opti " + i, titleColor);
 
                                  }
// Baut das Benutzermenu auf der Seite auf
                                  if (i <= this.anzMw) {
// optSet: Gesetzte Optionen
                                      this.addAndFillCell(headers, "MW " + i, titleColor);
// optParams: Eventuell notwendige Parameter
                                  }
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
                              }
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return String mit dem HTML-Code
function getForm(optSet, optParams = { }) {
    const __FORM = '<form id="options" method="POST"><table><tbody><tr>';
    const __FORMEND = '</tr></tbody></table></form>';
    const __FORMWIDTH = getValue(optParams.formWidth, 3);
    const __FORMBREAK = getValue(optParams.formBreak, __FORMWIDTH);
    const __SHOWFORM = getOptValue(optSet.showForm, true) ? optParams.showForm : { 'showForm' : true };
    const __PRIOOPTS = groupData(optSet, opt => getOptConfig(opt).FormPrio);
    let form = __FORM;
    let count = 0;   // Bisher angezeigte Optionen
    let column = 0; // Spalte der letzten Option (1-basierend)
 
    for (let optKeys of Object.values(__PRIOOPTS)) {
        for (let optKey of optKeys) {
            if (checkItem(optKey, __SHOWFORM, optParams.hideForm)) {
                const __ELEMENT = getOptionElement(optSet[optKey]);
                const __TDOPT = ((~ __ELEMENT.indexOf('|')) ? "" : ' colspan="2"');


                              // Titel fuer die Werte mit Ende 18
                if (__ELEMENT) {
                              if (this.trE) {
                    if (++count > __FORMBREAK) {
                                  this.addAndFillCell(headers, "tr." + this.kennzE, titleColor);
                        if (++column > __FORMWIDTH) {
                              }
                            column = 1;
                              if (this.antHptE) {
                        }
                                  this.addAndFillCell(headers, "%H" + this.kennzE, titleColor);
                    }
                              }
                    if (column === 1) {
                              if (this.antNebE) {
                        form += '</tr><tr>';
                                  this.addAndFillCell(headers, "%N" + this.kennzE, titleColor);
                    }
                              }
                    form += '\n<td' + __TDOPT + '>' + __ELEMENT.replace('|', '</td><td>') + '</td>';
                              if (this.priE) {
                }
                                  this.addAndFillCell(headers, "Prios" + this.kennzE, titleColor);
            }
                              }
        }
                              if (this.skillE) {
    }
                                  this.addAndFillCell(headers, "Skill" + this.kennzE, titleColor);
    form += '\n' + __FORMEND;
                              }
 
                              for (let i = 1; i <= 6; i++) {
    return form;
                                  if (i <= this.anzOptiE) {
}
                                      this.addAndFillCell(headers, "Opti " + i + this.kennzE, titleColor);
                                  }
                                  if (i <= this.anzMwE) {
                                      this.addAndFillCell(headers, "MW " + i + this.kennzE, titleColor);
                                  }
                              }
                          },  // Ende addTitles()
        'addValues'     : function(player, playerRow, color = "#FFFFFF") {
                              const __COLOR = (player.isGoalie ? getColor("TOR") : color);
                              const __POS1COLOR = getColor(player.getPos());


                              // Aktuelle Werte
// Fuegt das Script in die Seite ein
                              if (this.tal) {
// optSet: Gesetzte Optionen
                                  this.addAndFillCell(playerRow, player.getTalent(), __COLOR);
// optParams: Eventuell notwendige Parameter
                              }
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
                              if (this.quo) {
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
                                  this.addAndFillCell(playerRow, player.getAufwertungsSchnitt(), __COLOR, 2);
// return String mit dem HTML-Code fuer das Script
                              }
function getScript(optSet, optParams = { }) {
                              if (this.aufw) {
    //const __SCRIPT = '<script type="text/javascript">function activateMenu() { console.log("TADAAA!"); }</script>';
                                  this.addAndFillCell(playerRow, player.getAufwert(), __COLOR);
    //const __SCRIPT = '<script type="text/javascript">\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionRst(key, value) { alert("RESET"); }\n</script>';
                              }
    //const __FORM = '<form method="POST"><input type="button" id="showOpts" name="showOpts" value="Optionen anzeigen" onclick="activateMenu()" /></form>';
                              if (this.geb) {
    const __SCRIPT = "";
                                  this.addAndFillCell(playerRow, player.getGeb(), __COLOR, 0);
 
                              }
    //window.eval('function activateMenu() { console.log("TADAAA!"); }');
                              if (this.substAge) {
 
                                  convertStringFromHTML(playerRow.cells, this.colIdx.Age, function(unused) {
    return __SCRIPT;
                                                                                              return parseFloat(player.getAge()).toFixed(2);
}
                                                                                          });
                              } else if (this.alter) {
                                  this.addAndFillCell(playerRow, player.getAge(), __COLOR, 2);
                              }
                              if (this.fix) {
                                  this.addAndFillCell(playerRow, player.getFixSkills(), __COLOR, 0);
                              }
                              if (this.tr) {
                                  this.addAndFillCell(playerRow, player.getTrainableSkills(), __COLOR, 0);
                              }
                              if (this.antHpt) {
                                  this.addAndFillCell(playerRow, player.getPriPercent(player.getPos()), __COLOR, 0);
                              }
                              if (this.antNeb) {
                                  this.addAndFillCell(playerRow, player.getSecPercent(player.getPos()), __COLOR, 0);
                              }
                              if (this.pri) {
                                  this.addAndFillCell(playerRow, player.getPrios(player.getPos()), __COLOR, 1);
                              }
                              if (this.skill) {
                                  this.addAndFillCell(playerRow, player.getSkill(), __COLOR, 2);
                              }
                              if (this.pos) {
                                  this.addAndFillCell(playerRow, player.getPos(), __POS1COLOR);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
                                  const __COLI = getColor(__POSI);


                                  if (i <= this.anzOpti) {
// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
                                      if ((i === 1) || ! player.isGoalie) {
// anchor: Element, das als Anker fuer die Anzeige dient
                                          // Opti anzeigen
// optSet: Gesetzte Optionen
                                          this.addAndFillCell(playerRow, player.getOpti(__POSI), __COLI, 2);
// optParams: Eventuell notwendige Parameter
                                      } else {
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
                                          // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
                                          this.addCell(playerRow);
// 'formWidth': Anzahl der Elemente pro Zeile
                                      }
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
                                  }
function buildForm(anchor, optSet, optParams = { }) {
                                  if (i <= this.anzMw) {
    __LOG[3]("buildForm()");
                                      if ((i === 1) || ! player.isGoalie) {
 
                                          // MW anzeigen
    const __FORM = getForm(optSet, optParams);
                                          this.addAndFillCell(playerRow, player.getMarketValue(__POSI), __COLI, 0);
    const __SCRIPT = getScript(optSet, optParams);
                                      } else {
                                          // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                              }


                              // Werte mit Ende 18
    addForm(anchor, __FORM, __SCRIPT);
                              if (this.trE) {
}
                                  this.addAndFillCell(playerRow, player.getTrainableSkills(player.__TIME.end), __COLOR, 1);
                              }
                              if (this.antHptE) {
                                  this.addAndFillCell(playerRow, player.getPriPercent(player.getPos(), player.__TIME.end), __COLOR, 0);
                              }
                              if (this.antNebE) {
                                  this.addAndFillCell(playerRow, player.getSecPercent(player.getPos(), player.__TIME.end), __COLOR, 0);
                              }
                              if (this.priE) {
                                  this.addAndFillCell(playerRow, player.getPrios(player.getPos(), player.__TIME.end), __COLOR, 1);
                              }
                              if (this.skillE) {
                                  this.addAndFillCell(playerRow, player.getSkill(player.__TIME.end), __COLOR, 2);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
                                  const __COLI = getColor(__POSI);


                                  if (i <= this.anzOptiE) {
// Informationen zu hinzugefuegten Forms
                                      if ((i === 1) || ! player.isGoalie) {
const __FORMS = { };
                                          // Opti anzeigen
                                          this.addAndFillCell(playerRow, player.getOpti(__POSI, player.__TIME.end), __COLI, 2);
                                      } else {
                                          // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                                  if (i <= this.anzMwE) {
                                      if ((i === 1) || ! player.isGoalie) {
                                          // MW anzeigen
                                          this.addAndFillCell(playerRow, player.getMarketValue(__POSI, player.__TIME.end), __COLI, 0);
                                      } else {
                                          // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                              }
                          }  // Ende addValues(player, playerRow)
    } );


// Klasse PlayerRecord ******************************************************************
// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
// anchor: Element, das als Anker fuer die Anzeige dient
// form: HTML-Form des Optionsmenu (hinten angefuegt)
// script: Script mit Reaktionen
function addForm(anchor, form = "", script = "") {
    const __OLDFORM = __FORMS[anchor];
    const __REST = (__OLDFORM === undefined) ? anchor.innerHTML :
                  anchor.innerHTML.substring(0, anchor.innerHTML.length - __OLDFORM.Script.length - __OLDFORM.Form.length);


function PlayerRecord(age, skills, isGoalie) {
    __FORMS[anchor] = {
    'use strict';
                          'Script' : script,
                          'Form'   : form
                      };


     this.mwFormel = this.__MWFORMEL.S10; // Neue Formel, genauer in initPlayer()
     anchor.innerHTML = __REST + script + form;
}


    this.age = age;
// ==================== Abschnitt fuer Klasse Classification ====================
    this.skills = skills;
    this.isGoalie = isGoalie;


    // in this.initPlayer() definiert:
// Basisklasse fuer eine Klassifikation der Optionen nach Kriterium (z.B. Erst- und Zweitteam oder Fremdteam)
    // this.zatGeb: ZAT, an dem der Spieler Geburtstag hat, -1 fuer "noch nicht zugewiesen", also '?'
function Classification() {
    // this.zatAge: Bisherige erfolgte Trainings-ZATs
     'use strict';
    // this.talent: Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
     // this.aufwert: Aufwertungsstring
    // this.mwFormel: Benutzte MW-Formel, siehe __MWFORMEL
    // this.positions[][]: Positionstext und Opti; TOR-Index ist 5
    // this.skillsEnd[]: Berechnet aus this.skills, this.age und aktuellerZat


     // in this calcZusatz()/setZusatz() definiert:
    this.renameFun = prefixName;
     // this.trainiert: Anzahl der erfolgreichen Trainingspunkte
     //this.renameParamFun = undefined;
     // this.bestPos: erster (bester) Positionstext
     this.optSet = undefined;
     this.optSelect = { };
}
}


Class.define(PlayerRecord, Object, {
Class.define(Classification, Object, {
        '__TIME'               : {   // Zeitpunktangaben
                    'renameOptions' : function() {
                                      'cre' : 0,  // Jugendspieler angelegt (mit 12 Jahren)
                                          const __PARAM = this.renameParamFun();
                                      'beg' : 1,  // Jugendspieler darf trainieren (wird 13 Jahre alt)
 
                                      'now' : 2,  // Aktueller ZAT
                                          if (__PARAM !== undefined) {
                                      'end' : 3  // Jugendspieler wird Ende 18 gezogen (Geb. - 1 bzw. ZAT 71 fuer '?')
                                              // Klassifizierte Optionen umbenennen...
                                  },
                                              return renameOptions(this.optSet, this.optSelect, __PARAM, this.renameFun);
        '__MWFORMEL'            : {  // Zu benutzende Marktwertformel
                                          } else {
                                      'alt' : 0,  // Marktwertformel bis Saison 9 inklusive
                                              return Promise.resolve();
                                       'S10' : 1  // Marktwertformel MW5 ab Saison 10
                                          }
                                  },
                                       },
        'toString'             : function() {
                    'deleteOptions' : function(ignList) {
                                      let result = "Alter\t\t" + this.age + "\n\n";
                                          const __OPTSELECT = addProps([], this.optSelect, null, ignList);
                                      result += "Aktuelle Werte\n";
                                      result += "Skillschnitt\t" + this.getSkill().toFixed(2) + '\n';
                                      result += "Optis und Marktwerte";


                                      for (let pos of this.positions) {
                                           return deleteOptions(this.optSet, __OPTSELECT, true, true);
                                           result += "\n\t" + pos + '\t';
                                          result += this.getOpti(pos).toFixed(2) + '\t';
                                          result += getNumberString(this.getMarketValue(pos).toString());
                                       }
                                       }
                });


                                      result += "\n\nWerte mit Ende 18\n";
// ==================== Ende Abschnitt fuer Klasse Classification ====================
                                      result += "Skillschnitt\t" + this.getSkill(this.__TIME.end).toFixed(2) + '\n';
                                      result += "Optis und Marktwerte";


                                      for (let pos of this.positions) {
// ==================== Abschnitt fuer Klasse TeamClassification ====================
                                          result += "\n\t" + this.getPos()[i] + '\t';
                                          result += this.getOpti(pos, this.__TIME.end).toFixed(2) + '\t';
                                          result += getNumberString(this.getMarketValue(pos, this.__TIME.end).toString());
                                      }


                                      return result;
// Klasse fuer die Klassifikation der Optionen nach Team (Erst- und Zweitteam oder Fremdteam)
                                  },  // Ende this.toString()
function TeamClassification() {
        'initPlayer'            : function(saison, currZAT, gebZAT, tclass, progresses) {
    'use strict';
                                      // Berechnet die Opti-Werte, sortiert das Positionsfeld und berechnet die Einzelskills mit Ende 18
                                      this.zatGeb = gebZAT;
                                      this.zatAge = this.calcZatAge(currZAT);
                                      this.talent = tclass;
                                      this.aufwert = progresses;
                                      this.mwFormel = ((saison < 10) ? this.__MWFORMEL.alt : this.__MWFORMEL.S10);


                                      const __POSREIHEN = [ "ABW", "DMI", "MIT", "OMI", "STU", "TOR" ];
    Classification.call(this);
                                      this.positions = [];
                                      for (let index = 0; index < __POSREIHEN.length; index++) {
                                          const __REIHE = __POSREIHEN[index];


                                          this.positions[index] = [ __REIHE, this.getOpti(__REIHE) ];
    this.team = undefined;
                                      }
    this.teamParams = undefined;
}


                                      // Sortieren
Class.define(TeamClassification, Classification, {
                                      sortPositionArray(this.positions);
                    'renameParamFun' : function() {
                                          const __MYTEAM = (this.team = getMyTeam(this.optSet, this.teamParams, this.team));


                                      // Einzelskills mit Ende 18 berechnen
                                          if (__MYTEAM.LdNr) {
                                      this.skillsEnd = [];
                                              // Prefix fuer die Optionen mit gesonderten Behandlung...
                                              return __MYTEAM.LdNr.toString() + '.' + __MYTEAM.LgNr.toString() + ':';
                                          } else {
                                              return undefined;
                                          }
                                      }
                });


                                      const __ZATDONE = this.getZatDone();
// ==================== Ende Abschnitt fuer Klasse TeamClassification ====================
                                      const __ZATTOGO = this.getZatDone(this.__TIME.end) - __ZATDONE;
                                      const __ADDRATIO = (__ZATDONE ? __ZATTOGO / __ZATDONE : 0);
                                      let addSkill = (__ADDRATIO ? __ADDRATIO * this.getTrainiert() : __ZATTOGO * (1 + this.talent / 3.6));


                                      for (let i in this.skills) {
// ==================== Abschnitt fuer Klasse Team ====================
                                          const __SKILL = this.skills[i];
                                          let progSkill = __SKILL;


                                          if (isTrainableSkill(i)) {
// Klasse fuer Teamdaten
                                              // Auf ganze Zahl runden und parseInt(), da das sonst irgendwie als String interpretiert wird
function Team(team, land, liga) {
                                              const __ADDSKILL = Math.min(getMulValue(__ADDRATIO, __SKILL, 0, NaN), 99 - progSkill);
    'use strict';


                                              progSkill += __ADDSKILL;
    this.Team = team;
                                              addSkill -= __ADDSKILL;
    this.Land = land;
                                          }
    this.Liga = liga;
    this.LdNr = getLandNr(land);
    this.LgNr = getLigaNr(liga);
}


                                          this.skillsEnd[i] = progSkill;
Class.define(Team, Object, {
                                      }
                    '__TEAMITEMS' : {   // Items, die in Team als Teamdaten gesetzt werden...
                                      this.restEnd = addSkill;
                                        'Team' : true,
                                  }, // Ende this.initPlayer()
                                        'Liga' : true,
        'setZusatz'             : function(zatAge, trainiert, bestPos) {
                                        'Land' : true,
                                      // Setzt Nebenwerte fuer den Spieler (geht ohne initPlayer())
                                        'LdNr' : true,
                                      this.zatAge = zatAge;
                                        'LgNr' : true
                                      this.trainiert = trainiert;
                                    }
                                      this.bestPos = bestPos;
                });
                                  },
        'calcZusatz'           : function() {
                                      // Ermittelt Nebenwerte fuer den Spieler und gibt sie alle zurueck (nach initPlayer())
                                      // this.zatAge und this.skills bereits in initPlayer() berechnet
                                      this.trainiert = this.getTrainiert(true);  // neu berechnet aus Skills
                                      this.bestPos = this.getPos(-1); // hier: -1 explizit angeben, da neu ermittelt (this.bestPos noch nicht belegt)


                                      return {
// ==================== Ende Abschnitt fuer Klasse Team ====================
                                                'zatAge'    : this.zatAge,
                                                'trainiert'  : this.trainiert,
                                                'bestPos'    : this.bestPos
                                            };
                                  },
        'getGeb'                : function() {
                                      return (this.zatGeb < 0) ? '?' : this.zatGeb;
                                  },
        'calcZatAge'            : function(currZAT) {
                                      let zatAge;


                                      if (this.zatGeb !== undefined) {
// ==================== Abschnitt fuer Klasse Verein ====================
                                          let ZATs = (this.age - ((currZAT < this.zatGeb) ? 12 : 13)) * 72;  // Basiszeit fuer die Jahre seit Jahrgang 13


                                          if (this.zatGeb < 0) {
// Klasse fuer Vereinsdaten
                                              zatAge = ZATs + currZAT; // Zaehlung begann Anfang der Saison (und der Geburtstag wird erst nach dem Ziehen bestimmt)
function Verein(team, land, liga, id, manager, flags) {
                                          } else {
    'use strict';
                                              zatAge = ZATs + currZAT - this.zatGeb;  // Verschiebung relativ zum Geburtstag (von -zatGeb, ..., 0, ..., 71 - zatGeb)
 
                                          }
    Team.call(this, team, land, liga);
                                      }
 
    this.ID = id;
    this.Manager = manager;
    this.Flags = (flags || []);
}


                                      return zatAge;
Class.define(Verein, Team, {
                                  },
                    '__TEAMITEMS' : {   // Items, die in Verein als Teamdaten gesetzt werden...
        'getZatAge'             : function(when = this.__TIME.now) {
                                        'Team'    : true,
                                      if (when === this.__TIME.end) {
                                        'Liga'   : true,
                                          return (18 - 12) * 72 - 1;  // (max.) Trainings-ZATs bis Ende 18
                                        'Land'    : true,
                                      } else {
                                        'LdNr'   : true,
                                          return this.zatAge;
                                        'LgNr'    : true,
                                      }
                                        'ID'     : true,
                                  },
                                        'Manager' : true,
        'getZatDone'           : function(when = this.__TIME.now) {
                                        'Flags'   : true
                                      return Math.max(0, this.getZatAge(when));
                                    }
                                  },
                });
        'getAge'               : function(when = this.__TIME.now) {
                                      if (this.mwFormel === this.__MWFORMEL.alt) {
                                          return (when === this.__TIME.end) ? 18 : this.age;
                                      } else {  // Geburtstage ab Saison 10...
                                          return (13.00 + this.getZatAge(when) / 72);
                                      }
                                  },
        'getTrainiert'         : function(recalc = false) {
                                      if (recalc || (this.trainiert === undefined)) {
                                          return this.getTrainableSkills();
                                      } else {
                                          return this.trainiert;
                                      }
                                  },
        'getAufwertungsSchnitt' : function() {
                                      return parseFloat(this.getTrainiert() / this.getZatDone());
                                  },
        'getPos'               : function(idx = undefined) {
                                      const __IDXOFFSET = 1;


                                      switch (getValue(idx, 0)) {
// ==================== Ende Abschnitt fuer Klasse Verein ====================
                                      case -1 : return (this.bestPos = this.positions[this.isGoalie ? 5 : 0][0]);
                                      case  0 : return this.bestPos;
                                      default : return this.positions[idx - __IDXOFFSET][0];
                                      }
                                  },
        'getTalent'            : function() {
                                      return (this.talent < 0) ? "wenig" : (this.talent > 0) ? "hoch" : "normal";
                                  },
        'getAufwert'            : function() {
                                      return this.aufwert;
                                  },
        'getSkillSum'          : function(when = this.__TIME.now, idxSkills = undefined, restRate = 15) {
                                      let cachedItem;


                                      if (idxSkills === undefined) {  // Gesamtsumme ueber alle Skills wird gecached...
// ==================== Spezialisierter Abschnitt fuer Optionen ====================
                                          cachedItem = ((when === this.__TIME.end) ? 'skillSumEnd' : 'skillSum');


                                          const __CACHED = this[cachedItem];
// Gesetzte Optionen (wird von initOptions() angelegt und von loadOptions() gefuellt):
const __OPTSET = { };


                                          if (__CACHED !== undefined) {
// Teamparameter fuer getrennte Speicherung der Optionen fuer Erst- und Zweitteam...
                                              return __CACHED;
const __TEAMCLASS = new TeamClassification();
                                          }


                                          idxSkills = getIdxAllSkills();
// Optionen mit Daten, die ZAT- und Team-bezogen gemerkt werden...
                                      }
__TEAMCLASS.optSelect = {
                            'datenZat'        : true,
                            'oldDatenZat'    : true,
                            'fingerprints'    : true,
                            'birthdays'      : true,
                            'tClasses'        : true,
                            'progresses'      : true,
                            'ziehAnz'        : true,
                            'ziehAnzAufstieg' : true,
                            'zatAges'        : true,
                            'trainiert'      : true,
                            'positions'      : true,
                            'skills'          : true,
                            'foerderung'      : true
                        };


                                      const __SKILLS = ((when === this.__TIME.end) ? this.skillsEnd : this.skills);
// Gibt die Teamdaten zurueck und aktualisiert sie ggfs. in der Option
                                      let sumSkills = (when === this.__TIME.end) ? (restRate / 15) * this.restEnd : 0;
// optSet: Platz fuer die gesetzten Optionen
// teamParams: Dynamisch ermittelte Teamdaten ('Team', 'Liga', 'Land', 'LdNr' und 'LgNr')
// myTeam: Objekt fuer die Teamdaten
// return Die Teamdaten oder undefined bei Fehler
function getMyTeam(optSet = undefined, teamParams = undefined, myTeam = new Team()) {
    if (teamParams !== undefined) {
        addProps(myTeam, teamParams, myTeam.__TEAMITEMS);
        __LOG[2]("Ermittelt: " + safeStringify(myTeam));
        // ... und abspeichern, falls erweunscht...
        if (optSet && optSet.team) {
            setOpt(optSet.team, myTeam, false);
        }
    } else {
        const __TEAM = ((optSet && optSet.team) ? getOptValue(optSet.team) : undefined); // Gespeicherte Parameter


                                      for (let idx of idxSkills) {
        if ((__TEAM !== undefined) && (__TEAM.Land !== undefined)) {
                                          sumSkills += __SKILLS[idx];
            addProps(myTeam, __TEAM, myTeam.__TEAMITEMS);
                                      }
            __LOG[2]("Gespeichert: " + safeStringify(myTeam));
        } else {
            __LOG[6]("Team nicht ermittelt: " + safeStringify(__TEAM));
        }
    }


                                      if (cachedItem !== undefined) {
    return myTeam;
                                          this[cachedItem] = sumSkills;
}
                                      }
 
// Behandelt die Optionen und laedt das Benutzermenu
// optConfig: Konfiguration der Optionen
// optSet: Platz fuer die gesetzten Optionen
// optParams: Eventuell notwendige Parameter zur Initialisierung
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
// 'teamParams': Getrennte Daten-Option wird genutzt, hier: Team() mit 'LdNr'/'LgNr' des Erst- bzw. Zweitteams
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return Promise auf gefuelltes Objekt mit den gesetzten Optionen
function buildOptions(optConfig, optSet = undefined, optParams = { 'hideMenu' : false }) {
    // Klassifikation ueber Land und Liga des Teams...
    __TEAMCLASS.optSet = optSet; // Classification mit optSet verknuepfen
    __TEAMCLASS.teamParams = optParams.teamParams;  // Ermittelte Parameter


                                      return sumSkills;
    return startOptions(optConfig, optSet, __TEAMCLASS).then(optSet => {
                                  },
                    if (optParams.getDonation) {
        'getSkill'              : function(when = this.__TIME.now) {
                        // Jugendfoerderung aus der Options-HTML-Seite ermitteln...
                                      return this.getSkillSum(when) / 17;
                        const __BOXDONATION = document.getElementsByTagName('option');
                                  },
                        const __DONATION = getSelectionFromComboBox(__BOXDONATION, 10000, 'Number');
        'getOpti'              : function(pos, when = this.__TIME.now) {
                                      const __SUMALLSKILLS = this.getSkillSum(when);
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);


                                      return (4 * __SUMPRISKILLS + __SUMALLSKILLS) / 27;
                        __LOG[3]("Jugendf\xF6rderung: " + __DONATION + " Euro");
                                  },
        'getPrios'              : function(pos, when = this.__TIME.now) {
                                      return this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4) / 4;
                                  },
        'getPriPercent'        : function(pos, when = this.__TIME.now) {
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __SUMSECSKILLS = this.getSkillSum(when, getIdxSecSkills(pos), 7);


                                      return (100 * __SUMPRISKILLS) / (__SUMPRISKILLS + __SUMSECSKILLS);
                        // ... und abspeichern...
                                  },
                        setOpt(optSet.foerderung, __DONATION, false);
        'getSecPercent'        : function(pos, when = this.__TIME.now) {
                    }
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __SUMSECSKILLS = this.getSkillSum(when, getIdxSecSkills(pos), 7);


                                      return (100 * __SUMSECSKILLS) / (__SUMPRISKILLS + __SUMSECSKILLS);
                    return showOptions(optSet, optParams);
                                  },
                }, defaultCatch);
        'getTrainableSkills'    : function(when = this.__TIME.now) {
}
                                      return this.getSkillSum(when, getIdxTrainableSkills());
                                  },
        'getFixSkills'          : function() {
                                      return this.getSkillSum(this.__TIME.now, getIdxFixSkills());
                                  },
        'getMarketValue'        : function(pos, when = this.__TIME.now) {
                                      const __AGE = this.getAge(when);


                                      if (this.mwFormel === this.__MWFORMEL.alt) {
// ==================== Ende Abschnitt fuer Optionen ====================
                                          return Math.round(Math.pow((1 + this.getSkill(when)/100) * (1 + this.getOpti(pos, when)/100) * (2 - __AGE/100), 10) * 2);    // Alte Formel bis Saison 9
                                      } else {  // MW-Formel ab Saison 10...
                                          const __MW5TF = 1.00;  // Zwischen 0.97 und 1.03


                                          return Math.round(Math.pow(1 + this.getSkill(when)/100, 5.65) * Math.pow(1 + this.getOpti(pos, when)/100, 8.1) * Math.pow(1 + (100 - __AGE)/49, 10) * __MW5TF);
// ==================== Abschnitt genereller Code zur Anzeige der Jugend ====================
                                      }
                                  }
    } );


// Funktionen fuer die HTML-Seite *******************************************************
// Funktionen ***************************************************************************


// Liest eine Zahl aus der Spalte einer Zeile der Tabelle aus (z.B. Alter, Geburtsdatum)
// Erschafft die Spieler-Objekte und fuellt sie mit Werten
// cells: Die Zellen einer Zeile
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// colIdxInt: Spaltenindex der gesuchten Werte
// optSet: Gesetzte Optionen (und Config)
// return Spalteneintrag als Zahl (-1 fuer "keine Zahl", undefined fuer "nicht gefunden")
// colIdx: Liste von Spaltenindices der gesuchten Werte
function getIntFromHTML(cells, colIdxInt) {
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
     const __CELL = getValue(cells[colIdxInt], { });
// offsetLower: Ignorierte Zeilen unterhalb der Daten
    const __TEXT = __CELL.textContent;
// page: 1: Teamuebersicht, 2: Spielereinzelwerte, 3: Opt. Skill, 4: Optionen, Default: 0
function init(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, page = 0) {
     storePlayerDataFromHTML(playerRows, optSet, colIdx, offsetUpper, offsetLower, page);


     if (__TEXT !== undefined) {
     const __SAISON = getOptValue(optSet.saison);
        try {
    const __AKTZAT = getOptValue(optSet.aktuellerZat);
            const __VALUE = parseInt(__TEXT, 10);
    const __GEALTERT = ((__AKTZAT >= 72) ? (getIntFromHTML(playerRows[playerRows.length - offsetLower - 1].cells, colIdx.Age) < 13) : false);
    const __CURRZAT = (__GEALTERT ? 0 : __AKTZAT);
    const __LGNR = __TEAMCLASS.team.LgNr;
    const __KLASSE = (__LGNR > 1) ? (__LGNR > 3) ? 3 : 2 : 1;
    const __DONATION = getOptValue(optSet.foerderung);
    const __BIRTHDAYS = getOptValue(optSet.birthdays, []);
    const __TCLASSES = getOptValue(optSet.tClasses, []);
    const __PROGRESSES = getOptValue(optSet.progresses, []);
    const __ZATAGES = getOptValue(optSet.zatAges, []);
    const __TRAINIERT = getOptValue(optSet.trainiert, []);
    const __POSITIONS = getOptValue(optSet.positions, []);
    const __SKILLS = getOptValue(optSet.skills, []);
    const __ISSKILLPAGE = (page === 2);
    const __BASEDATA = [ __BIRTHDAYS, __TCLASSES, __PROGRESSES ];  // fuer initPlayer
    const __DATA = (__ISSKILLPAGE ? [ __SKILLS, __BASEDATA ] : [ __BASEDATA, __SKILLS ]);  // fuer initPlayer: [0] = von HTML-Seite, [1] = aus gespeicherten Daten
    const __IDMAP = getPlayerIdMap(optSet);
    const __CATIDS = __IDMAP.catIds;
    const __PLAYERS = [];


            if (! isNaN(__VALUE)) {
    __LOG[5](__IDMAP);
                return __VALUE;
            }
        } catch (ex) { }


         return -1;
    for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++) {
        const __CELLS = playerRows[i].cells;
 
         if (__CELLS.length > 1) {
            const __LAND = getStringFromHTML(__CELLS, colIdx.Land);
            const __AGE = getIntFromHTML(__CELLS, colIdx.Age);
            const __ISGOALIE = isGoalieFromHTML(__CELLS, colIdx.Age);
            const __AKTION = getElementFromHTML(__CELLS, colIdx.Akt);
 
            const __NEWPLAYER = new PlayerRecord(__LAND, __AGE, __ISGOALIE, __SAISON, __CURRZAT, __DONATION);
 
            __NEWPLAYER.initPlayer(__DATA[0], j, __ISSKILLPAGE);
 
            const __IDX = selectPlayerIndex(__NEWPLAYER, j, __CATIDS);
 
            __NEWPLAYER.initPlayer(__DATA[1], __IDX, ! __ISSKILLPAGE);
 
            __NEWPLAYER.prognoseSkills();
 
            if (! __ISSKILLPAGE) {
                __NEWPLAYER.setZusatz(__ZATAGES[__IDX], __TRAINIERT[__IDX], __POSITIONS[__IDX]);
            }
 
            __NEWPLAYER.createWarnDraw(__AKTION, __KLASSE);
 
            __PLAYERS[j++] = __NEWPLAYER;
        }
    }
 
    if (__ISSKILLPAGE) {
        calcPlayerData(__PLAYERS, optSet);
    } else {
        setPlayerData(__PLAYERS, optSet);
     }
     }


     return undefined;
    storePlayerIds(__PLAYERS, optSet);
 
     return __PLAYERS;
}
}


// Liest eine Dezimalzahl aus der Spalte einer Zeile der Tabelle aus
// Berechnet die Identifikations-IDs (Fingerprints) der Spieler neu und speichert diese
// cells: Die Zellen einer Zeile
function getPlayerIdMap(optSet) {
// colIdxInt: Spaltenindex der gesuchten Werte
    const __FINGERPRINTS = getOptValue(optSet.fingerprints, []);
// return Spalteneintrag als Dezimalzahl (undefined fuer "keine Zahl" oder "nicht gefunden")
     const __MAP = {
function getFloatFromHTML(cells, colIdxFloat) {
                      'ids'    : { },
     const __CELL = getValue(cells[colIdxFloat], { });
                      'cats'  : [],
    const __TEXT = __CELL.textContent;
                      'catIds' : { }
                  };
    const __IDS = __MAP.ids;
    const __CATS = __MAP.cats;
    const __CATIDS = __MAP.catIds;
 
    for (let i = 0; i < __FINGERPRINTS.length; i++) {
        const __ID = __FINGERPRINTS[i];
        const __CAT = PlayerRecord.prototype.getCatFromFingerPrint(__ID);


    if (__TEXT !== undefined) {
        if (__ID) {
        try {
            if (! __CATIDS[__CAT]) {
             return parseFloat(__TEXT);
                __CATIDS[__CAT] = { };
         } catch (ex) { }
            }
             __IDS[__ID] = i;
            __CATS[i] = __CAT;
            __CATIDS[__CAT][__ID] = i;
         }
     }
     }


     return undefined;
     return __MAP;
}
}


// Liest einen String aus der Spalte einer Zeile der Tabelle aus
// Berechnet die Identifikations-IDs (Fingerprints) der Spieler neu und speichert diese
// cells: Die Zellen einer Zeile
// players: Array von PlayerRecord mit den Spielerdaten
// colIdxStr: Spaltenindex der gesuchten Werte
// optSet: Gesetzte Optionen (und Config)
// return Spalteneintrag als String ("" fuer "nicht gefunden")
function storePlayerIds(players, optSet) {
function getStringFromHTML(cells, colIdxStr) {
     const __FINGERPRINTS = [];
     const __CELL = getValue(cells[colIdxStr], { });
    const __TEXT = __CELL.textContent;


     return getValue(__TEXT.toString(), "");
     for (let i = 0; i < players.length; i++) {
}
        const __PLAYER = players[i];


// Liest die Talentklasse ("wenig", "normal", "hoch") aus der Spalte einer Zeile der Tabelle aus
        if ((__PLAYER.zatGeb !== undefined) && (__PLAYER.talent !== undefined) && (__PLAYER.positions !== undefined)) {
// cells: Die Zellen einer Zeile
            __FINGERPRINTS[i]  = __PLAYER.getFingerPrint();
// colIdxStr: Spaltenindex der gesuchten Werte
        }
// return Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
    }
function getTalentFromHTML(cells, colIdxTal) {
    const __TEXT = getStringFromHTML(cells, colIdxTal);


     return parseInt((__TEXT === "wenig") ? -1 : (__TEXT === "hoch") ? +1 : 0, 10);
     setOpt(optSet.fingerprints, __FINGERPRINTS, false);
}
}


// Liest die Einzelskills aus der Spalte einer Zeile der Tabelle aus
// Sucht fuer den Spieler den Eintrag aus catIds heraus und gibt den (geloeschten) Index zurueck
// cells: Die Zellen einer Zeile
// player: PlayerRecord mit den Daten eines Spielers
// colIdx: Liste von Spaltenindices der gesuchten Werte mit den Eintraegen
// index: Position des Spielers im neuen Array von Spielerdaten
// 'Einz' (erste Spalte) und 'Zus' (Spalte hinter dem letzten Eintrag)
// catIds: PlayerIdMap zum Finden des Spielers ueber die Spielerdaten
// return Skills als Array von Zahlen
// return Original-Index der Daten dieses Spielers im Array von Spielerdaten
function getSkillsFromHTML(cells, colIdx) {
function selectPlayerIndex(player, index, catIds) {
     const __RESULT = [];
     const __MYCAT = player.getCat();
    const __CATS = catIds[__MYCAT];
    const __ID = player.findInFingerPrints(__CATS);
    let idx = index;


     for (let i = colIdx.Einz; i < colIdx.Zus; i++) {
     if (__ID !== undefined) {
         __RESULT[i - colIdx.Einz] = getIntFromHTML(cells, i);
         idx = __CATS[__ID];
        delete __CATS[__ID];
     }
     }


     return __RESULT;
     return idx;
}
}


// Liest aus, ob der Spieler Torwart oder Feldspieler ist
// Speichtert die abgeleiteten Werte in den Spieler-Objekten
// cells: Die Zellen einer Zeile
// players: Array von PlayerRecord mit den Spielerdaten
// colIdxClass: Spaltenindex einer fuer TOR eingefaerbten Zelle
// optSet: Gesetzte Optionen (und Config)
// return Angabe, der Spieler Torwart oder Feldspieler ist
function setPlayerData(players, optSet) {
function isGoalieFromHTML(cells, colIdxClass) {
     const __ZIEHANZAHL = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
     return (cells[colIdxClass].className === "TOR");
    let ziehAnzAufstieg = 0;
}
    const __ZATAGES = [];
    const __TRAINIERT = [];
    const __POSITIONS = [];


// Liest einen String aus der Spalte einer Zeile der Tabelle aus, nachdem dieser konvertiert wurde
    for (let i = 0; i < players.length; i++) {
// cells: Die Zellen einer Zeile
        const __ZUSATZ = players[i].calcZusatz();
// colIdxStr: Spaltenindex der gesuchten Werte
// convertFun: Funktion, die den Wert konvertiert
// return Spalteneintrag als String ("" fuer "nicht gefunden")
function convertStringFromHTML(cells, colIdxStr, convertFun = sameValue) {
    const __CELL = getValue(cells[colIdxStr], { });
    const __TEXT = convertFun(__CELL.textContent, __CELL);


    if (__TEXT !== undefined) {
        if (__ZUSATZ.zatAge !== undefined) { // braucht Geburtstag fuer gueltige Werte!
        __CELL.innerHTML = __TEXT;
            const __INDEX = players[i].calcZiehIndex(); // Lfd. Nummer des Abrechnungsmonats (0-basiert)
    }


     return getValue(__TEXT.toString(), "");
            if ((__INDEX >= 0) && (__INDEX < __ZIEHANZAHL.length)) {
                __ZIEHANZAHL[__INDEX]++;
            }
 
            __ZATAGES[i] = __ZUSATZ.zatAge;
        }
        if (players[i].isZiehAufstieg()) {
            ziehAnzAufstieg++;
        }
        __TRAINIERT[i] = __ZUSATZ.trainiert;
        __POSITIONS[i] = __ZUSATZ.bestPos;
    }
 
     setOpt(optSet.ziehAnz, __ZIEHANZAHL, false);
    setOpt(optSet.ziehAnzAufstieg, ziehAnzAufstieg, false);
    setOpt(optSet.zatAges, __ZATAGES, false);
    setOpt(optSet.trainiert, __TRAINIERT, false);
    setOpt(optSet.positions, __POSITIONS, false);
}
}


// Konvertiert den Aufwertungstext einer Zelle auf der Jugend-Teamuebersicht
// Berechnet die abgeleiteten Werte in den Spieler-Objekten neu und speichert diese
// value: Der Inhalt dieser Zeile ("+1 SKI +1 OPT" bzw. "+2 SKI)
// players: Array von PlayerRecord mit den Spielerdaten
// cell: Zelle, in der der Text stand (optional)
// optSet: Gesetzte Optionen (und Config)
// return Der konvertierte String ("SKI OPT" bzw. "SKI SKI")
function calcPlayerData(players, optSet) {
function convertAufwertung(value, cell = undefined) {
     const __ZATAGES = [];
     if (value !== undefined) {
    const __TRAINIERT = [];
        value = value.replace(/\+2 (\w+)/, "$1 $1").replace(/\+1 /g, "");
    const __POSITIONS = [];
 
    for (let i = 0; i < players.length; i++) {
        const __ZUSATZ = players[i].calcZusatz();


         if (cell && (cell.className === "TOR")) {
         if (__ZUSATZ.zatAge !== undefined) { // braucht Geburtstag fuer gueltige Werte!
             value = convertGoalieSkill(value);
             __ZATAGES[i] = __ZUSATZ.zatAge;
         }
         }
        __TRAINIERT[i] = __ZUSATZ.trainiert;
        __POSITIONS[i] = __ZUSATZ.bestPos;
     }
     }


     return value;
     setOpt(optSet.zatAges, __ZATAGES, false);
    setOpt(optSet.trainiert, __TRAINIERT, false);
    setOpt(optSet.positions, __POSITIONS, false);
}
}


// Konvertiert die allgemeinen Skills in die eines Torwarts
// Ermittelt die fuer diese Seite relevanten Werte in den Spieler-Objekten aus den Daten der Seite und speichert diese
// value: Ein Text, der die Skillnamen enthaelt
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// return Der konvertierte String mit Aenderungen (z.B. "FAN" statt "KOB") oder unveraendert
// optSet: Gesetzte Optionen (und Config)
function convertGoalieSkill(value) {
// colIdx: Liste von Spaltenindices der gesuchten Werte
     if (value !== undefined) {
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
        value = value.replace(/\w+/g, getGoalieSkill);
// offsetLower: Ignorierte Zeilen unterhalb der Daten
     }
// page: 1: Teamuebersicht, 2: Spielereinzelwerte, 3: Opt. Skill, 4: Optionen, Default: 0
function storePlayerDataFromHTML(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, page = 0) {
     const __COLDEFS = [ { }, {
                                'birthdays'  : { 'name' : 'birthdays', 'getFun' : getIntFromHTML, 'params' : [ colIdx.Geb ] },
                                'tClasses'  : { 'name' : 'tClasses', 'getFun' : getTalentFromHTML, 'params' : [ colIdx.Tal ] },
                                'progresses' : { 'name' : 'progresses', 'getFun' : getAufwertFromHTML, 'params' : [ colIdx.Auf, getOptValue(optSet.shortAufw, true) ] }
                            }, {
                                'skills'     : { 'name' : 'skills', 'getFun' : getSkillsFromHTML, 'params' : [ colIdx ]}
                            } ][getValueIn(page, 1, 2, 0)];


     return value;
     return storePlayerDataColsFromHTML(playerRows, optSet, __COLDEFS, offsetUpper, offsetLower);
}
}


// Konvertiert einen Aufwertungstext fuer einen Skillnamen in den fuer einen Torwart
// Ermittelt bestimmte Werte in den Spieler-Objekten aus den Daten der Seite und speichert diese
// name: Allgemeiner Skillname (abgeleitet von den Feldspielern)
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// return Der konvertierte String (z.B. "FAN" statt "KOB") oder unveraendert
// optSet: Gesetzte Optionen (und Config)
function getGoalieSkill(name) {
// colDefs: Informationen zu ausgewaehlten Datenspalten
     const __GOALIESKILLS = {
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
                              'SCH' : 'ABS',
// offsetLower: Ignorierte Zeilen unterhalb der Daten
                              'BAK' : 'STS',
function storePlayerDataColsFromHTML(playerRows, optSet, colDefs, offsetUpper = 1, offsetLower = 0) {
                              'KOB' : 'FAN',
     const __DATA = { };
                              'ZWK' : 'STB',
 
                              'DEC' : 'SPL',
    for (let key in colDefs) {
                              'GES' : 'REF'
        __DATA[key] = [];
                          };
    }


     return getValue(__GOALIESKILLS[name], name);
     for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++) {
}
        const __CELLS = playerRows[i].cells;


// Liest die Aufwertungen eines Spielers aus und konvertiert je nachdem, ob der Spieler Torwart oder Feldspieler ist
        if (__CELLS.length > 1) {
// cells: Die Zellen einer Zeile
            for (let key in colDefs) {
// colIdxAuf: Spaltenindex der gesuchten Aufwertungen
                const __COLDEF = colDefs[key];
// shortForm: true = abgekuerzt, false = Originalform
// return Konvertierte Aufwertungen (kurze oder lange Form, aber in jedem Fall fuer Torwart konvertiert)
function getAufwertFromHTML(cells, colIdxAuf, shortForm = true) {
    const __ISGOALIE = isGoalieFromHTML(cells, colIdxAuf);
    const __TEXT = convertStringFromHTML(cells, colIdxAuf, (shortForm ? convertAufwertung : __ISGOALIE ? convertGoalieSkill : undefined));


    return (__ISGOALIE ? __TEXT.replace(/\w+/g, getGoalieSkill) : __TEXT);
                __DATA[key][j] = __COLDEF.getFun(__CELLS, ...__COLDEF.params);
}
            }
            j++;
        }
    }


// Identitaetsfunktion. Konvertiert nichts, sondern liefert einfach den Wert zurueck
    for (let key in colDefs) {
// value: Der uebergebene Wert
        const __COLDEF = colDefs[key];
// return Derselbe Wert
function sameValue(value) {
    return value;
}


// Liefert den ganzzeiligen Anteil einer Zahl zurueck, indem alles hinter einem Punkt abgeschnitten wird
        __LOG[7]('Schreibe ' + __COLDEF.name + ': ' + __DATA[key]);
// value: Eine uebergebene Dezimalzahl
// return Der ganzzeilige Anteil dieser Zahl
function floorValue(value, dot = '.') {
    const __INDEXDOT = (value ? value.indexOf(dot) : -1);


    return ((~ __INDEXDOT) ? value.substring(0, __INDEXDOT) : value);
        setOpt(optSet[__COLDEF.name], __DATA[key], false);
    }
}
}


// Hilfsfunktionen **********************************************************************
// Trennt die Gruppen (z.B. Jahrgaenge) mit Linien
function separateGroups(rows, borderString, colIdxSort = 0, offsetUpper = 1, offsetLower = 0, offsetLeft = -1, offsetRight = 0, formatFun = sameValue) {
    if (offsetLeft < 0) {
        offsetLeft = colIdxSort;  // ab Sortierspalte
    }


// Sortiert das Positionsfeld per BubbleSort
    for (let i = offsetUpper, newVal, oldVal = formatFun((rows[i].cells[colIdxSort] || { }).textContent); i < rows.length - offsetLower - 1; i++, oldVal = newVal) {
function sortPositionArray(array) {
        newVal = formatFun((rows[i + 1].cells[colIdxSort] || { }).textContent);
    const __TEMP = [];
        if (newVal !== oldVal) {
    let transposed = true;
            for (let j = offsetLeft; j < rows[i].cells.length - offsetRight; j++) {
    // TOR soll immer die letzte Position im Feld sein, deshalb - 1
                 rows[i].cells[j].style.borderBottom = borderString;
    let length = array.length - 1;
 
    while (transposed && (length > 1)) {
        transposed = false;
        for (let i = 0; i < length - 1; i++) {
            // Vergleich Opti-Werte:
            if (array[i][1] < array[i + 1][1]) {
                // vertauschen
                __TEMP[0] = array[i][0];
                __TEMP[1] = array[i][1];
                array[i][0] = array[i + 1][0];
                array[i][1] = array[i + 1][1];
                array[i + 1][0] = __TEMP[0];
                 array[i + 1][1] = __TEMP[1];
                transposed = true;
             }
             }
         }
         }
        length--;
     }
     }
}
}


// Fuegt in die uebergebene Zahl Tausender-Trennpunkte ein
// Klasse ColumnManager *****************************************************************
// Wandelt einen etwaig vorhandenen Dezimalpunkt in ein Komma um
function getNumberString(numberString) {
    if (numberString.lastIndexOf(".") !== -1) {
        // Zahl enthaelt Dezimalpunkt
        const __VORKOMMA = numberString.substring(0, numberString.lastIndexOf("."));
        const __NACHKOMMA = numberString.substring(numberString.lastIndexOf(".") + 1, numberString.length);


        return getNumberString(__VORKOMMA) + "," + __NACHKOMMA;
function ColumnManager(optSet, colIdx, showCol) {
    } else {
    'use strict';
        // Kein Dezimalpunkt, fuege Tausender-Trennpunkte ein:
        // String umdrehen, nach jedem dritten Zeichen Punkt einfuegen, dann wieder umdrehen:
        const __TEMP = reverseString(numberString);
        let result = "";


        for (let i = 0; i < __TEMP.length; i++) {
    __LOG[3]("ColumnManager()");
            if ((i > 0) && (i % 3 === 0)) {
                result += ".";
            }
            result += __TEMP.substr(i, 1);
        }


        return reverseString(result);
    const __SHOWCOL = getValue(showCol, true);
     }
     const __SHOWALL = ((__SHOWCOL === true) || (__SHOWCOL.Default === true));
}


// Dreht den uebergebenen String um
    const __BIRTHDAYS = getOptValue(optSet.birthdays, []).length;
function reverseString(string) {
    const __TCLASSES = getOptValue(optSet.tClasses, []).length;
     let result = "";
     const __PROGRESSES = getOptValue(optSet.progresses, []).length;


     for (let i = string.length - 1; i >= 0; i--) {
     const __ZATAGES = getOptValue(optSet.zatAges, []).length;
        result += string.substr(i, 1);
    const __TRAINIERT = getOptValue(optSet.trainiert, []).length;
    }
    const __POSITIONS = getOptValue(optSet.positions, []).length;


     return result;
     const __EINZELSKILLS = getOptValue(optSet.skills, []).length;
}
    const __PROJECTION = (__EINZELSKILLS && __ZATAGES);


// Schaut nach, ob der uebergebene Index zu einem trainierbaren Skill gehoert
     this.colIdx = colIdx;
// Die Indizes gehen von 0 (SCH) bis 16 (EIN)
function isTrainableSkill(idx) {
     const __TRAINABLESKILLS = getIdxTrainableSkills();
    const __IDX = parseInt(idx, 10);
    let result = false;


     for (let idxTrainable of __TRAINABLESKILLS) {
     this.saison = getOptValue(optSet.saison);
        if (__IDX === idxTrainable) {
    this.gt = getOptValue(optSet.zeigeJahrgang);
            result = true;
    this.gtUxx = getOptValue(optSet.zeigeUxx);
            break;
        }
    }


     return result;
     this.fpId = (__BIRTHDAYS && __TCLASSES && __POSITIONS && getValue(__SHOWCOL.zeigeId, __SHOWALL) && getOptValue(optSet.zeigeId));
}
    this.warn = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnung, __SHOWALL) && getOptValue(optSet.zeigeWarnung));
 
    this.warnMonth = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungMonat, __SHOWALL) && getOptValue(optSet.zeigeWarnungMonat));
// Gibt die Indizes aller Skills zurueck
    this.warnHome = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungHome, __SHOWALL) && getOptValue(optSet.zeigeWarnungHome));
function getIdxAllSkills() {
    this.warnDialog = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungDialog, __SHOWALL) && getOptValue(optSet.zeigeWarnungDialog));
     return [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
    this.warnAufstieg = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungAufstieg, __SHOWALL) && getOptValue(optSet.zeigeWarnungAufstieg));
    this.warnLegende = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungLegende, __SHOWALL) && getOptValue(optSet.zeigeWarnungLegende));
    this.bar = (__PROJECTION && getValue(__SHOWCOL.zeigeBalken, __SHOWALL) && getOptValue(optSet.zeigeBalken));
    this.barAbs = getOptValue(optSet.absBalken);
    this.donor = getOptValue(optSet.foerderung);
    this.geb = (__BIRTHDAYS && getValue(__SHOWCOL.zeigeGeb, __SHOWALL) && getOptValue(optSet.zeigeGeb));
    this.tal = (__TCLASSES && getValue(__SHOWCOL.zeigeTal, __SHOWALL) && getOptValue(optSet.zeigeTal));
    this.quo = (__ZATAGES && __TRAINIERT && getValue(__SHOWCOL.zeigeQuote, __SHOWALL) && getOptValue(optSet.zeigeQuote));
    this.aufw = (__PROGRESSES && getValue(__SHOWCOL.zeigeAufw, __SHOWALL) && getOptValue(optSet.zeigeAufw));
    this.substAge = (__ZATAGES && getValue(__SHOWCOL.ersetzeAlter, __SHOWALL) && getOptValue(optSet.ersetzeAlter));
    this.alter = (__ZATAGES && getValue(__SHOWCOL.zeigeAlter, __SHOWALL) && getOptValue(optSet.zeigeAlter));
    this.fix = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeFixSkills, __SHOWALL) && getOptValue(optSet.zeigeFixSkills));
    this.tr = (__EINZELSKILLS && __TRAINIERT && getValue(__SHOWCOL.zeigeTrainiert, __SHOWALL) && getOptValue(optSet.zeigeTrainiert));
    this.zat = (__ZATAGES && getValue(__SHOWCOL.zeigeZatDone, __SHOWALL) && getOptValue(optSet.zeigeZatDone));
     this.antHpt = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeAnteilPri, __SHOWALL) && getOptValue(optSet.zeigeAnteilPri));
    this.antNeb = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeAnteilSec, __SHOWALL) && getOptValue(optSet.zeigeAnteilSec));
    this.pri = (__EINZELSKILLS && getValue(__SHOWCOL.zeigePrios, __SHOWALL) && getOptValue(optSet.zeigePrios));
    this.skill = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeSkill, __SHOWALL) && getOptValue(optSet.zeigeSkill));
    this.pos = (__EINZELSKILLS && __POSITIONS && getValue(__SHOWCOL.zeigePosition, __SHOWALL) && getOptValue(optSet.zeigePosition));
    this.anzOpti = ((__EINZELSKILLS && getValue(__SHOWCOL.zeigeOpti, __SHOWALL)) ? getOptValue(optSet.anzahlOpti) : 0);
    this.anzMw =  ((__PROJECTION && getValue(__SHOWCOL.zeigeMW, __SHOWALL)) ? getOptValue(optSet.anzahlMW) : 0);
    this.substSkills = (__PROJECTION && getValue(__SHOWCOL.ersetzeSkills, __SHOWALL) && getOptValue(optSet.ersetzeSkills));
    this.trE = (__PROJECTION && __TRAINIERT && getValue(__SHOWCOL.zeigeTrainiertEnde, __SHOWALL) && getOptValue(optSet.zeigeTrainiertEnde));
    this.zatE = (__ZATAGES && getValue(__SHOWCOL.zeigeZatLeft, __SHOWALL) && getOptValue(optSet.zeigeZatLeft));
    this.antHptE = (__PROJECTION && getValue(__SHOWCOL.zeigeAnteilPriEnde, __SHOWALL) && getOptValue(optSet.zeigeAnteilPriEnde));
    this.antNebE = (__PROJECTION && getValue(__SHOWCOL.zeigeAnteilSecEnde, __SHOWALL) && getOptValue(optSet.zeigeAnteilSecEnde));
    this.priE = (__PROJECTION && getValue(__SHOWCOL.zeigePriosEnde, __SHOWALL) && getOptValue(optSet.zeigePriosEnde));
    this.skillE = (__PROJECTION && getValue(__SHOWCOL.zeigeSkillEnde, __SHOWALL) && getOptValue(optSet.zeigeSkillEnde));
    this.anzOptiE = ((__PROJECTION && getValue(__SHOWCOL.zeigeOptiEnde, __SHOWALL)) ? getOptValue(optSet.anzahlOptiEnde) : 0);
    this.anzMwE = ((__PROJECTION && getValue(__SHOWCOL.zeigeMWEnde, __SHOWALL)) ? getOptValue(optSet.anzahlMWEnde) : 0);
    this.kennzE = getOptValue(optSet.kennzeichenEnde);
}
}


// Gibt die Indizes der trainierbaren Skills zurueck
Class.define(ColumnManager, Object, {
function getIdxTrainableSkills() {
        'toString'      : function() { // Bisher nur die noetigsten Parameter ausgegeben...
    return [ 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 15 ];
                              let result = "Skillschnitt\t\t" + this.skill + '\n';
}
                              result += "Beste Position\t" + this.pos + '\n';
                              result += "Optis\t\t\t" + this.anzOpti + '\n';
                              result += "Marktwerte\t\t" + this.anzMw + '\n';
                              result += "Skillschnitt Ende\t" + this.skillE + '\n';
                              result += "Optis Ende\t\t" + this.anzOptiE + '\n';
                              result += "Marktwerte Ende\t" + this.anzMwE + '\n';


// Gibt die Indizes der Fixskills zurueck
                              return result;
function getIdxFixSkills() {
                          },
    return [ 6, 7, 12, 13, 14, 16 ];
        'addCell'        : function(tableRow) {
}
                              return tableRow.insertCell(-1);
                          },
        'addAndFillCell' : function(tableRow, value, color, align, digits = 2) {
                              let text = value;


// Gibt die Indizes der Primaerskills zurueck
                              if ((value || (value === 0)) && isFinite(value) && (value !== true) && (value !== false)) {
function getIdxPriSkills(pos) {
                                  // Zahl einfuegen
    switch (pos) {
                                  if (value < 1000) {
        case "TOR" : return [ 2, 3, 4, 5 ];
                                      // Mit Nachkommastellen darstellen
        case "ABW" : return [ 2, 3, 4, 15 ];
                                      text = parseFloat(value).toFixed(digits);
        case "DMI" : return [ 1, 4, 9, 11 ];
                                  } else {
        case "MIT" : return [ 1, 3, 9, 11 ];
                                      // Mit Tausenderpunkten darstellen
        case "OMI" : return [ 1, 5, 9, 11 ];
                                      text = getNumberString(value.toString());
        case "STU" : return [ 0, 2, 3, 5 ];
                                  }
        default :    return [];
                              }
    }
}


// Gibt die Indizes der (trainierbaren) Sekundaerskills zurueck
                              // String, Boolean oder Zahl einfuegen...
function getIdxSecSkills(pos) {
                              const __CELL = this.addCell(tableRow);
    switch (pos) {
        case "TOR" : return [ 0, 1, 8, 9, 10, 11, 15 ];
        case "ABW" : return [ 0, 1, 5, 8, 9, 10, 11 ];
        case "DMI" : return [ 0, 2, 3, 5, 8, 10, 15 ];
        case "MIT" : return [ 0, 2, 4, 5, 8, 10, 15 ];
        case "OMI" : return [ 0, 2, 3, 4, 8, 10, 15 ];
        case "STU" : return [ 1, 4, 8, 9, 10, 11, 15 ];
        default :    return [];
    }
}


// Gibt die zur Position gehoerige Farbe zurueck
                              __CELL.innerHTML = text;
function getColor(pos) {
                              if (color) {
    switch (pos) {
                                  __CELL.style.color = color;
        case "TOR" : return "#FFFF00";
                              }
        case "ABW" : return "#00FF00";
                              if (align) {
        case "DMI" : return "#3366FF";
                                  __CELL.align = align;
        case "MIT" : return "#66FFFF";
                              }
        case "OMI" : return "#FF66FF";
        case "STU" : return "#FF0000";
        case "LEI" : return "#FFFFFF";
        default :    return "";
    }
}


// ==================== Ende Abschnitt genereller Code zur Anzeige der Jugend ====================
                              return __CELL;
                          },
        'addAndBarCell'  : function(tableRow, value, scale = 100, offset = 0, width = 100, height = 10, zoom = 100) {
                              const __VALUE = ((scale && isFinite(value)) ? ((value - offset) / Math.max(1, scale - offset) * 100) : 0);


// ==================== Abschnitt fuer interne IDs auf den Seiten ====================
                              // HTML-Code fuer Anteilsbalken einfuegen...
                              const __CELL = this.addCell(tableRow);


const __GAMETYPES = {    // "Blind FSS gesucht!"
                              __CELL.innerHTML = this.getBarImg(__VALUE, width, height, zoom);
        'unbekannt'  : -1,
                              __CELL.align = 'left';
        "reserviert" :  0,
        "Frei"      :  0,
        "spielfrei"  :  0,
        "Friendly"  :  1,
        "Liga"      :  2,
        "LP"        :  3,
        "OSEQ"      :  4,
        "OSE"        :  5,
        "OSCQ"      :  6,
        "OSC"        :  7
    };


const __LIGANRN = {
                              return __CELL;
        'unbekannt'  :  0,
                          },
         '1. Liga'   : 1,
         'getBarImg'     : function(value, width = 100, height = 10, zoom = 100) {
        '2. Liga A'  :  2,
                              const __IMAGE = Math.min(99, Math.max(0, getMulValue(value, 1, 0, 0)));
        '2. Liga B'  :  3,
                              const __LENGTH = getMulValue(width / 100, getMulValue(zoom / 100, value, 0, 0), 0, 0);
        '3. Liga A'  :  4,
                              const __WIDTH = Math.min(width, __LENGTH);
        '3. Liga B'  :  5,
                              const __HEIGHT = Math.max(3, getMulValue(zoom / 100, height * (__LENGTH / __WIDTH), 0, 0));
        '3. Liga C'  :  6,
        '3. Liga D'  :  7
    };


const __LANDNRN = {
                              // HTML-Code fuer Anteilsbalken...
        'unbekannt'             :  0,
                              return '<img src="images/balken/' + __IMAGE + '.GIF" width="' + __WIDTH + '" height=' + __HEIGHT + '>';
        'Albanien'               :  45,
                          },
        'Andorra'               :  95,
         'addTitles'     : function(headers, titleColor = "#FFFFFF") {
        'Armenien'               :  83,
                              // Spaltentitel zentrieren
         'Aserbaidschan'         : 104,
                              headers.align = "center";
        'Belgien'                :  12,
 
        'Bosnien-Herzegowina'    :  66,
                              // Titel fuer die aktuellen Werte
        'Bulgarien'              :  42,
                              if (this.fpId) {
        'D\xE4nemark'            :  8,
                                  this.addAndFillCell(headers, "Identifikation", titleColor);
        'Deutschland'            :  6,
                              }
        'England'                :  1,
                              if (this.bar) {
        'Estland'                :  57,
                                  this.addAndFillCell(headers, "Qualit\xE4t", titleColor);
        'Far\xF6er'              :  68,
                              }
        'Finnland'              :  40,
                              if (this.tal) {
        'Frankreich'            :  32,
                                  this.addAndFillCell(headers, "Talent", titleColor);
        'Georgien'              :  49,
                              }
        'Griechenland'          :  30,
                              if (this.quo) {
        'Irland'                :  5,
                                  this.addAndFillCell(headers, "Quote", titleColor);
        'Island'                :  29,
                              }
        'Israel'                :  23,
                              if (this.aufw) {
        'Italien'                :  10,
                                  this.addAndFillCell(headers, "Aufwertung", titleColor);
        'Kasachstan'            : 105,
                              }
        'Kroatien'              :  24,
                              if (this.geb) {
        'Lettland'              :  97,
                                  this.addAndFillCell(headers, "Geb.", titleColor);
        'Liechtenstein'          :  92,
                              }
        'Litauen'                :  72,
                              if (this.alter && ! this.substAge) {
        'Luxemburg'              :  93,
                                  this.addAndFillCell(headers, "Alter", titleColor);
        'Malta'                  :  69,
                              }
        'Mazedonien'            :  86,
                              if (this.fix) {
        'Moldawien'              :  87,
                                  this.addAndFillCell(headers, "fix", titleColor);
        'Niederlande'            :  11,
                              }
        'Nordirland'            :  4,
                              if (this.tr) {
        'Norwegen'              :  9,
                                  this.addAndFillCell(headers, "tr.", titleColor);
        '\xD6sterreich'          :  14,
                              }
        'Polen'                  :  25,
                              if (this.zat) {
        'Portugal'              :  17,
                                  this.addAndFillCell(headers, "ZAT", titleColor);
        'Rum\xE4nien'            :  28,
                              }
        'Russland'              :  19,
                              if (this.antHpt) {
        'San Marino'            :  98,
                                  this.addAndFillCell(headers, "%H", titleColor);
        'Schottland'            :  2,
                              }
        'Schweden'              :  27,
                              if (this.antNeb) {
        'Schweiz'                :  37,
                                  this.addAndFillCell(headers, "%N", titleColor);
        'Serbien und Montenegro' :  41,
                              }
        'Slowakei'              :  70,
                              if (this.pri) {
        'Slowenien'              :  21,
                                  this.addAndFillCell(headers, "Prios", titleColor);
        'Spanien'                :  13,
                              }
        'Tschechien'            :  18,
                              if (this.skill) {
        'T\xFCrkei'              :  39,
                                  this.addAndFillCell(headers, "Skill", titleColor);
        'Ukraine'                :  20,
                              }
        'Ungarn'                :  26,
                              if (this.pos) {
        'Wales'                  :  3,
                                  this.addAndFillCell(headers, "Pos", titleColor);
        'Weissrussland'          :  71,
                              }
        'Zypern'                :  38
                              for (let i = 1; i <= 6; i++) {
    };
                                  if (i <= this.anzOpti) {
                                      this.addAndFillCell(headers, "Opti " + i, titleColor);
                                  }
                                  if (i <= this.anzMw) {
                                      this.addAndFillCell(headers, "MW " + i, titleColor);
                                  }
                              }


// ==================== Abschnitt fuer Daten des Spielplans ====================
                              // Titel fuer die Werte mit Ende 18
 
                              if (this.trE) {
// Gibt die ID fuer den Namen eines Wettbewerbs zurueck
                                  this.addAndFillCell(headers, "tr." + this.kennzE, titleColor);
// gameType: Name des Wettbewerbs eines Spiels
                              }
// return OS2-ID fuer den Spieltyp (1 bis 7), 0 fuer spielfrei/Frei/reserviert, -1 fuer ungueltig
                              if (this.zatE) {
function getGameTypeID(gameType) {
                                  this.addAndFillCell(headers, "ZAT" + this.kennzE, titleColor);
    return getValue(__GAMETYPES[gameType], __GAMETYPES.unbekannt);
                              }
}
                              if (this.antHptE) {
 
                                  this.addAndFillCell(headers, "%H" + this.kennzE, titleColor);
// Gibt die ID des Landes mit dem uebergebenen Namen zurueck.
                              }
// land: Name des Landes
                              if (this.antNebE) {
// return OS2-ID des Landes, 0 fuer ungueltig
                                  this.addAndFillCell(headers, "%N" + this.kennzE, titleColor);
function getLandNr(land) {
                              }
    return getValue(__LANDNRN[land], __LANDNRN.unbekannt);
                              if (this.priE) {
}
                                  this.addAndFillCell(headers, "Prios" + this.kennzE, titleColor);
                              }
                              if (this.skillE) {
                                  this.addAndFillCell(headers, "Skill" + this.kennzE, titleColor);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  if (i <= this.anzOptiE) {
                                      this.addAndFillCell(headers, "Opti " + i + this.kennzE, titleColor);
                                  }
                                  if (i <= this.anzMwE) {
                                      this.addAndFillCell(headers, "MW " + i + this.kennzE, titleColor);
                                  }
                              }
                          },  // Ende addTitles()
        'addValues'      : function(player, playerRow, color = "#FFFFFF") {
                              // Warnlevel des Spielers anpassen...
                              const __WARNDRAW = player.warnDraw || player.warnDrawAufstieg || __NOWARNDRAW;
                              __WARNDRAW.setWarn(this.warn, this.warnMonth, this.warnAufstieg);


// Gibt die ID der Liga mit dem uebergebenen Namen zurueck.
                              const __IDXPRI = getIdxPriSkills(player.getPos());
// land: Name der Liga
                              const __COLOR = __WARNDRAW.getColor(player.isGoalie ? getColor('TOR') : color); // Angepasst an Ziehwarnung
// return OS2-ID der Liga, 0 fuer ungueltig
                              const __POS1COLOR = getColor((player.getPosPercent() > 99.99) ? 'LEI' : player.getPos());
function getLigaNr(liga) {
                              const __OSBLAU = getColor("");
    return getValue(__LIGANRN[liga], __LIGANRN.unbekannt);
}


// ==================== Abschnitt fuer sonstige Parameter ====================
                              // Aktuelle Werte
                              if (this.fpId) {
                                  this.addAndFillCell(playerRow, player.getFingerPrint(), __COLOR);
                              }
                              if (this.bar) {
                                  const __VALUE = player.getPrios(player.getPos(), player.__TIME.end);
                                  const __SCALE = (this.barAbs ? 100 : (this.donor / 125));
                                  const __OFFSET = (this.barAbs ? 0 : Math.pow(__SCALE / 20, 2));
                                  const __ZOOM = 50 + __SCALE / 2;


const __TEAMSEARCHHAUPT = { // Parameter zum Team "<b>Willkommen im Managerb&uuml;ro von TEAM</b><br>LIGA LAND<a href=..."
                                  this.addAndBarCell(playerRow, __VALUE, __SCALE, __OFFSET, 100, 10, __ZOOM);
        'Zeile'  : 0,
                              }
        'Spalte' : 1,
                              if (this.tal) {
        'start'  : " von ",
                                  this.addAndFillCell(playerRow, player.getTalent(), __COLOR);
        'middle' : "</b><br>",
                              }
        'liga'  : ". Liga",
                              if (this.quo) {
        'land'  : ' ',
                                  this.addAndFillCell(playerRow, player.getAufwertungsSchnitt(), __COLOR, null, 2);
        'end'    : "<a href="
                              }
    };
                              if (this.colIdx.Auf) {
 
                                  convertStringFromHTML(playerRow.cells, this.colIdx.Auf, function(aufwert) {
const __TEAMSEARCHTEAM = {  // Parameter zum Team "<b>TEAM - LIGA <a href=...>LAND</a></b>"
                                                                                              return player.boldPriSkillNames(aufwert);
        'Zeile'  : 0,
                                                                                          });
        'Spalte' : 0,
                              }
        'start'  : "<b>",
                              if (this.aufw) {
        'middle' : " - ",
                                  this.addAndFillCell(playerRow, player.boldPriSkillNames(player.getAufwert()), __COLOR, 'left');
        'liga'  : ". Liga",
                              }
        'land'  : 'target="_blank">',
                              if (this.geb) {
        'end'    : "</a></b>"
                                  this.addAndFillCell(playerRow, player.getGeb(), __COLOR, null, 0);
    };
                              }
                              if (this.substAge) {
                                  convertStringFromHTML(playerRow.cells, this.colIdx.Age, function(unused) {
                                                                                              return parseFloat(player.getAge()).toFixed(2);
                                                                                          });
                              } else if (this.alter) {
                                  this.addAndFillCell(playerRow, player.getAge(), __COLOR, null, 2);
                              }
                              if (__WARNDRAW.monthDraw()) {  // Abrechnungszeitraum vor dem letztmoeglichen Ziehen...
                                  formatCell(playerRow.cells[this.colIdx.Age], true, __WARNDRAW.colAlert, null, 1.0);
                              }
                              if (this.fix) {
                                  this.addAndFillCell(playerRow, player.getFixSkills(), __COLOR, null, 0);
                              }
                              if (this.tr) {
                                  this.addAndFillCell(playerRow, player.getTrainableSkills(), __COLOR, null, 0);
                              }
                              if (this.zat) {
                                  this.addAndFillCell(playerRow, player.getZatDone(), __COLOR, null, 0);
                              }
                              if (this.antHpt) {
                                  this.addAndFillCell(playerRow, player.getPriPercent(player.getPos()), __COLOR, null, 0);
                              }
                              if (this.antNeb) {
                                  this.addAndFillCell(playerRow, player.getSecPercent(player.getPos()), __COLOR, null, 0);
                              }
                              if (this.pri) {
                                  this.addAndFillCell(playerRow, player.getPrios(player.getPos()), __COLOR, null, 1);
                              }
                              if (this.skill) {
                                  this.addAndFillCell(playerRow, player.getSkill(), __COLOR, null, 2);
                              }
                              if (this.pos) {
                                  this.addAndFillCell(playerRow, player.getPos(), __POS1COLOR);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
                                  const __COLI = getColor(__POSI);


// Ermittelt, wie das eigene Team heisst und aus welchem Land bzw. Liga es kommt (zur Unterscheidung von Erst- und Zweitteam)
                                  if (i <= this.anzOpti) {
// cell: Tabellenzelle mit den Parametern zum Team "startTEAMmiddleLIGA...landLANDend", LIGA = "#liga[ (A|B|C|D)]"
                                      if ((i === 1) || ! player.isGoalie) {
// teamSeach: Muster fuer die Suche, die Eintraege fuer 'start', 'middle', 'liga', 'land' und 'end' enthaelt
                                          // Opti anzeigen
// return Im Beispiel { 'Team' : "TEAM", 'Liga' : "LIGA", 'Land' : "LAND", 'LdNr' : LAND-NUMMER, 'LgNr' : LIGA-NUMMER },
                                          this.addAndFillCell(playerRow, player.getOpti(__POSI), __COLI, null, 2);
//        z.B. { 'Team' : "Choromonets Odessa", 'Liga' : "1. Liga", 'Land' : "Ukraine", 'LdNr' : 20, 'LgNr' : 1 }
                                      } else {
function getTeamParamsFromTable(table, teamSearch = undefined) {
                                          // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
    const __TEAMSEARCH  = getValue(teamSearch, __TEAMSEARCHHAUPT);
                                          this.addCell(playerRow);
    const __TEAMCELLROW  = getValue(__TEAMSEARCH.Zeile, 0);
                                      }
    const __TEAMCELLCOL  = getValue(__TEAMSEARCH.Spalte, 0);
                                  }
    const __TEAMCELLSTR  = (table === undefined) ? "" : table.rows[__TEAMCELLROW].cells[__TEAMCELLCOL].innerHTML;
                                  if (i <= this.anzMw) {
    const __SEARCHSTART  = __TEAMSEARCH.start;
                                      if ((i === 1) || ! player.isGoalie) {
    const __SEARCHMIDDLE = __TEAMSEARCH.middle;
                                          // MW anzeigen
    const __SEARCHLIGA  = __TEAMSEARCH.liga;
                                          this.addAndFillCell(playerRow, player.getMarketValue(__POSI), __COLI, null, 0);
    const __SEARCHLAND  = __TEAMSEARCH.land;
                                      } else {
    const __SEARCHEND    = __TEAMSEARCH.end;
                                          // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
    const __INDEXSTART  = __TEAMCELLSTR.indexOf(__SEARCHSTART);
                                          this.addCell(playerRow);
    const __INDEXEND    = __TEAMCELLSTR.indexOf(__SEARCHEND);
                                      }
                                  }
                              }


    let teamParams = __TEAMCELLSTR.substring(__INDEXSTART + __SEARCHSTART.length, __INDEXEND);
                              // Einzelwerte mit Ende 18
    const __INDEXLIGA = teamParams.indexOf(__SEARCHLIGA);
                              if (this.colIdx.Einz) {
    const __INDEXMIDDLE = teamParams.indexOf(__SEARCHMIDDLE);
                                  if (this.substSkills) {
 
                                      convertArrayFromHTML(playerRow.cells, this.colIdx.Einz, player.skillsEnd, function(value, cell, unused, index) {
    let land = (~ __INDEXLIGA) ? teamParams.substring(__INDEXLIGA + __SEARCHLIGA.length) : undefined;
                                                                                                                    if (~ __IDXPRI.indexOf(index)) {
    const __TEAMNAME = (~ __INDEXMIDDLE) ? teamParams.substring(0, __INDEXMIDDLE) : undefined;
                                                                                                                        formatCell(cell, true, __OSBLAU, __POS1COLOR, 1.0);
    let liga = ((~ __INDEXLIGA) && (~ __INDEXMIDDLE)) ? teamParams.substring(__INDEXMIDDLE + __SEARCHMIDDLE.length) : undefined;
                                                                                                                    }
 
                                                                                                                    return value;
    if (land !== undefined) {
                                                                                                                });
        if (land.charAt(2) === ' ') {   // Land z.B. hinter "2. Liga A " statt "1. Liga "
                                  } else {
            land = land.substr(2);
                                      convertArrayFromHTML(playerRow.cells, this.colIdx.Einz, player.skills.length, function(value, cell, unused, index) {
        }
                                                                                                                        if (~ __IDXPRI.indexOf(index)) {
        if (liga !== undefined) {
                                                                                                                            formatCell(cell, true, __POS1COLOR, null, 1.0);
            liga = liga.substring(0, liga.length - land.length);
                                                                                                                        }
        }
                                                                                                                        return value;
        const __INDEXLAND = land.indexOf(__SEARCHLAND);
                                                                                                                    });
         if (~ __INDEXLAND) {
                                  }
            land = land.substr(__INDEXLAND + __SEARCHLAND.length);
                              }
        }
                              if (this.trE) {
    }
                                  this.addAndFillCell(playerRow, player.getTrainableSkills(player.__TIME.end), __COLOR, null, 1);
                              }
                              if (this.zatE) {
                                  this.addAndFillCell(playerRow, player.getZatLeft(), __COLOR, null, 0);
                              }
                              if (this.antHptE) {
                                  this.addAndFillCell(playerRow, player.getPriPercent(player.getPos(), player.__TIME.end), __COLOR, null, 0);
                              }
                              if (this.antNebE) {
                                  this.addAndFillCell(playerRow, player.getSecPercent(player.getPos(), player.__TIME.end), __COLOR, null, 0);
                              }
                              if (this.priE) {
                                  this.addAndFillCell(playerRow, player.getPrios(player.getPos(), player.__TIME.end), __COLOR, null, 1);
                              }
                              if (this.skillE) {
                                  this.addAndFillCell(playerRow, player.getSkill(player.__TIME.end), __COLOR, null, 2);
                              }
                              for (let i = 1; i <= 6; i++) {
                                  const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
                                  const __COLI = getColor(__POSI);
 
                                  if (i <= this.anzOptiE) {
                                      if ((i === 1) || ! player.isGoalie) {
                                          // Opti anzeigen
                                          this.addAndFillCell(playerRow, player.getOpti(__POSI, player.__TIME.end), __COLI, null, 2);
                                      } else {
                                          // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                                  if (i <= this.anzMwE) {
                                      if ((i === 1) || ! player.isGoalie) {
                                          // MW anzeigen
                                          this.addAndFillCell(playerRow, player.getMarketValue(__POSI, player.__TIME.end), __COLI, null, 0);
                                      } else {
                                          // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                                          this.addCell(playerRow);
                                      }
                                  }
                              }
                          },  // Ende addValues(player, playerRow)
         'setGroupTitle'  : function(tableRow) {
                              if (this.gtUxx) {
                                  const __CELL = tableRow.cells[0];
                                  const __SAI = __CELL.innerHTML.match(/Saison (\d+)/)[1];
                                  const __JG = 13 + this.saison - __SAI;


    const __TEAM = new Team(__TEAMNAME, land, liga);
                                  __CELL.innerHTML = __CELL.innerHTML.replace('Jahrgang', 'U' + __JG + ' - $&');
                              }


    return __TEAM;
                              tableRow.style.display = (this.gt ? '' : 'none');
}
                          } // Ende setGroupTitle(tableRow)
    });


// Verarbeitet die URL der Seite und ermittelt die Nummer der gewuenschten Unterseite
// Klasse PlayerRecord ******************************************************************
// url: Adresse der Seite
// leafs: Liste von Filenamen mit der Default-Seitennummer (falls Query-Parameter nicht gefunden)
// item: Query-Parameter, der die Nummer der Unterseite angibt
// return Parameter aus der URL der Seite als Nummer
function getPageIdFromURL(url, leafs, item = "page") {
    const __URI = new URI(url);
    const __LEAF = __URI.getLeaf();


    for (let leaf in leafs) {
function PlayerRecord(land, age, isGoalie, saison, currZAT, donation) {
        if (__LEAF === leaf) {
    'use strict';
            const __DEFAULT = leafs[leaf];


            return getValue(__URI.getQueryPar(item), __DEFAULT);
    this.land = land;
        }
    this.age = age;
     }
     this.isGoalie = isGoalie;


     return -1;
     this.saison = saison;
}
    this.currZAT = currZAT;
    this.donation = donation;
    this.mwFormel = ((this.saison < 10) ? this.__MWFORMEL.alt : this.__MWFORMEL.S10);


// Gibt die laufende Nummer des ZATs im Text einer Zelle zurueck
    // in new PlayerRecord() definiert:
// cell: Tabellenzelle mit der ZAT-Nummer im Text
    // this.land: TLA des Geburtslandes
// return ZAT-Nummer im Text
    // this.age: Ganzzahliges Alter des Spielers
function getZATNrFromCell(cell) {
    // this.isGoalie: Angabe, ob es ein TOR ist
     const __TEXT = ((cell === undefined) ? [] : cell.textContent.split(' '));
     // this.mwFormel: Benutzte MW-Formel, siehe __MWFORMEL
     let ZATNr = 0;
     // this.donation: Jugendfoerderungsbetrag in Euro


     for (let i = 1; (ZATNr === 0) && (i < __TEXT.length); i++) {
     // in this.initPlayer() definiert:
        if (__TEXT[i - 1] === "ZAT") {
    // this.zatGeb: ZAT, an dem der Spieler Geburtstag hat, -1 fuer "noch nicht zugewiesen", also '?'
            if (__TEXT[i] !== "ist") {
    // this.zatAge: Bisherige erfolgte Trainings-ZATs
                ZATNr = parseInt(__TEXT[i], 10);
    // this.birth: Universell eindeutige Nummer des Geburtstags-ZATs des Spielers
            }
    // this.talent: Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
        }
     // this.aufwert: Aufwertungsstring
     }


     return ZATNr;
     // in this.calcSkills() definiert:
}
    // this.positions[][]: Positionstexte und Optis; TOR-Index ist 5
    // this.skills[]: Einzelskills
    // this.skillsEnd[]: Berechnet aus this.skills, this.age und aktuellerZat
    // this.zatLeft: ZATs bis zum Ende 18 (letzte Ziehmoeglichkeit)
    // this.restEnd: Korrekturterm zum Ausgleich von Rundungsfehlern mit Ende 18
    //              (also Skills, die nicht explizit in this.skillsEnd stehen)


// ==================== Ende Abschnitt fuer sonstige Parameter ====================
    // in this.calcZusatz()/setZusatz() definiert:
    // this.trainiert: Anzahl der erfolgreichen Trainingspunkte
    // indirekt this.zatAge und this.bestPos


// ==================== Hauptprogramm ====================
    // in this.createWarnDraw() definiert:
    // this.warnDraw: Behandlung von Warnungen Ende 18
    // this.warnDrawAufstieg: Behandlung von Warnungen bei Aufstieg


// Verarbeitet Ansicht "Haupt" (Managerbuero) zur Ermittlung des aktuellen ZATs
    // in this.getPos() definiert:
function procHaupt() {
     // this.bestPos: erster (bester) Positionstext
     const __TEAMPARAMS = getTeamParamsFromTable(getTable(1), __TEAMSEARCHHAUPT);  // Link mit Team, Liga, Land...
}


    buildOptions(__OPTCONFIG, __OPTSET, {
Class.define(PlayerRecord, Object, {
                    'teamParams' : __TEAMPARAMS,
        '__TIME'               : {  // Zeitpunktangaben
                    'hideMenu'  : true
                                      'cre' : 0, // Jugendspieler angelegt (mit 12 Jahren)
                });
                                      'beg' : 1,  // Jugendspieler darf trainieren (wird 13 Jahre alt)
                                      'now' : 2,  // Aktueller ZAT
                                      'end' : 3   // Jugendspieler wird Ende 18 gezogen (Geb. - 1 bzw. ZAT 71 fuer '?')
                                  },
        '__MWFORMEL'            : {  // Zu benutzende Marktwertformel
                                      'alt' : 0,  // Marktwertformel bis Saison 9 inklusive
                                      'S10' : 1  // Marktwertformel MW5 ab Saison 10
                                  },
        '__MAXPRISKILLS'        : 4 * 99,
        'toString'              : function() {  // Bisher nur die noetigsten Werte ausgegeben...
                                      let result = "Alter\t\t" + this.age + "\n\n";
                                      result += "Aktuelle Werte\n";
                                      result += "Skillschnitt\t" + this.getSkill().toFixed(2) + '\n';
                                      result += "Optis und Marktwerte";


    const __ZATCELL = getProp(getProp(getRows(0), 2), 'cells', { })[0];
                                      for (let pos of this.positions) {
    const __NEXTZAT = getZATNrFromCell(__ZATCELL);  // "Der naechste ZAT ist ZAT xx und ..."
                                          result += "\n\t" + pos + '\t';
    const __CURRZAT = __NEXTZAT - 1;
                                          result += this.getOpti(pos).toFixed(2) + '\t';
    const __DATAZAT = getOptValue(__OPTSET.datenZat);
                                          result += getNumberString(this.getMarketValue(pos).toString());
                                      }


    if (__CURRZAT >= 0) {
                                      result += "\n\nWerte mit Ende 18\n";
        __LOG[2]("Aktueller ZAT: " + __CURRZAT);
                                      result += "Skillschnitt\t" + this.getSkill(this.__TIME.end).toFixed(2) + '\n';
                                      result += "Optis und Marktwerte";


        // Neuen aktuellen ZAT speichern...
                                      for (let pos of this.positions) {
        setOpt(__OPTSET.aktuellerZat, __CURRZAT, false);
                                          result += "\n\t" + this.getPos()[i] + '\t';
                                          result += this.getOpti(pos, this.__TIME.end).toFixed(2) + '\t';
                                          result += getNumberString(this.getMarketValue(pos, this.__TIME.end).toString());
                                      }


         if (__CURRZAT !== __DATAZAT) {
                                      return result;
            __LOG[2](__LOG.changed(__DATAZAT, __CURRZAT));
                                  },  // Ende this.toString()
         'initPlayer'            : function(data, index, isSkillData = false) {  // isSkillData: true = Skilldaten, false = Basiswerte (Geb., Talent, Aufwertungen) oder keine
                                      if (data !== undefined) {
                                          if (isSkillData) {
                                              this.setSkills(data[index]);
                                          } else if (data.length >= 2){
                                              this.setGeb(data[0][index]);
                                              this.talent = data[1][index];
                                              this.aufwert = data[2][index];
                                          } else {
                                              // keine Daten
                                          }
                                      }
                                  },  // Ende this.initPlayer()
        'createWarnDraw'        : function(ziehmich = null, klasse = 1) {  // ziehmich: input Element zum Ziehen; klasse: Spielklasse 1, 2, 3
                                      // Objekte fuer die Verwaltung der Ziehwarnungen...
                                      this.warnDraw = undefined;
                                      this.warnDrawAufstieg = undefined;
                                      if (ziehmich) {
                                          const __LASTZAT = this.currZAT + this.getZatLeft();


            // ... und ZAT-bezogene Daten als veraltet markieren
                                          if (__LASTZAT < 72) {  // U19
             __TEAMCLASS.deleteOptions();
                                              this.warnDraw = new WarnDrawPlayer(this, getColor('STU'));  // rot
                                              __LOG[4](this.getAge().toFixed(2), "rot");
                                          } else if (__LASTZAT < Math.max(2, klasse) * 72) {  // Rest bis inkl. U18 (Liga 1 und 2) bzw. U17 (Liga 3)
                                              // do nothing
                                          } else if (__LASTZAT < (klasse + 1) * 72) {  // U17/U16 je nach Liga 2/3
                                              this.warnDrawAufstieg = new WarnDrawPlayer(this, getColor('OMI'));  // magenta
                                              this.warnDrawAufstieg.setAufstieg();
                                              __LOG[4](this.getAge().toFixed(2), "magenta");
                                          }
                                      }
                                  },  // Ende this.createWarnDraw()
        'setSkills'             : function(skills) {
                                      // Berechnet die Opti-Werte, sortiert das Positionsfeld und berechnet die Einzelskills mit Ende 18
                                    this.skills = skills;


            // Neuen Daten-ZAT speichern...
                                      const __POSREIHEN = [ 'ABW', 'DMI', 'MIT', 'OMI', 'STU', 'TOR' ];
            setOpt(__OPTSET.datenZat, __CURRZAT, false);
                                      this.positions = [];
        }
                                      for (let index = 0; index < __POSREIHEN.length; index++) {
    }
                                          const __REIHE = __POSREIHEN[index];
}
 
                                          this.positions[index] = [ __REIHE, this.getOpti(__REIHE) ];
                                      }
 
                                      // Sortieren
                                      sortPositionArray(this.positions);
                                  },  // Ende this.setSkills()
        'prognoseSkills'        : function() {
                                      // Einzelskills mit Ende 18 berechnen
                                      this.skillsEnd = [];
 
                                      const __ZATDONE = this.getZatDone();
                                      const __ZATTOGO = this.getZatLeft();
                                      const __ADDRATIO = (__ZATDONE ? __ZATTOGO / __ZATDONE : 0);
 
                                      let addSkill = __ZATTOGO * this.getAufwertungsSchnitt();
 
                                      for (let i in this.skills) {
                                          const __SKILL = this.skills[i];
                                          let progSkill = __SKILL;
 
                                          if (isTrainableSkill(i)) {
                                              // Auf ganze Zahl runden und parseInt(), da das sonst irgendwie als String interpretiert wird
                                              const __ADDSKILL = Math.min(99 - progSkill, getMulValue(__ADDRATIO, __SKILL, 0, NaN));


// Verarbeitet Ansicht "Teamuebersicht"
                                              progSkill += __ADDSKILL;
function procTeamuebersicht() {
                                              addSkill -= __ADDSKILL;
    const __ROWOFFSETUPPER = 1;     // Header-Zeile
                                          }
    const __ROWOFFSETLOWER = 1;     // Ziehen-Button


     const __COLUMNINDEX = {
                                          this.skillsEnd[i] = progSkill;
             'Age'  : 0,
                                      }
             'Geb'  : 1,
                                      this.restEnd = addSkill;
             'Flg'  : 2,
                                  },  // Ende this.prognoseSkills()
             'Land'  : 3,
        'setZusatz'            : function(zatAge, trainiert, bestPos) {
             'U'    : 4,
                                      // Setzt Nebenwerte fuer den Spieler (geht ohne initPlayer())
             'Skill' : 5,
                                      if (zatAge !== undefined) {
             'Tal'  : 6,
                                          this.zatAge = zatAge;
             'Akt'  : 7,
                                      }
             'Auf'  : 8,
                                      this.trainiert = trainiert;
             'Zus'  : 9
                                      this.bestPos = bestPos;
                                  },
        'calcZusatz'            : function() {
                                      // Ermittelt Nebenwerte fuer den Spieler und gibt sie alle zurueck (nach initPlayer())
                                      // this.zatAge und this.skills bereits in initPlayer() berechnet
                                      this.trainiert = this.getTrainiert(true);  // neu berechnet aus Skills
                                      let bestPos = this.getPos(-1);  // hier: -1 explizit angeben, damit neu ermittelt (falls this.bestPos noch nicht belegt)
 
                                      return {
                                                'zatAge'    : this.zatAge,
                                                'trainiert'  : this.trainiert,
                                                'bestPos'    : bestPos
                                            };
                                  },
        'getGeb'                : function() {
                                      return (this.zatGeb < 0) ? '?' : this.zatGeb;
                                  },
        'setGeb'                : function(gebZAT) {
                                      this.zatGeb = gebZAT;
                                      this.zatAge = this.calcZatAge(this.currZAT);
                                      this.birth = (36 + this.saison) * 72 + this.currZAT - this.zatAge;
                                  },
        'calcZatAge'            : function(currZAT) {
                                      let zatAge;
 
                                      if (this.zatGeb !== undefined) {
                                          let ZATs = 72 * (this.age - ((currZAT < this.zatGeb) ? 12 : 13));  // Basiszeit fuer die Jahre seit Jahrgang 13
 
                                          if (this.zatGeb < 0) {
                                              zatAge = ZATs + currZAT;  // Zaehlung begann Anfang der Saison (und der Geburtstag wird erst nach dem Ziehen bestimmt)
                                          } else {
                                              zatAge = ZATs + currZAT - this.zatGeb;  // Verschiebung relativ zum Geburtstag (von -zatGeb, ..., 0, ..., 71 - zatGeb)
                                          }
                                      }
 
                                      return zatAge;
                                  },
        'getZatAge'            : function(when = this.__TIME.now) {
                                      if (when === this.__TIME.end) {
                                          return (18 - 12) * 72 - 1;  // (max.) Trainings-ZATs bis Ende 18
                                      } else if (this.zatAge !== undefined) {
                                          return this.zatAge;
                                      } else {
                                          __LOG[4]("Empty getZatAge()");
 
                                          return NaN;
                                      }
                                  },
        'getZatDone'            : function(when = this.__TIME.now) {
                                      return Math.max(0, this.getZatAge(when));
                                  },
        'getZatLeft'            : function(when = this.__TIME.now) {
                                      if (this.zatLeft === undefined) {
                                          this.zatLeft = this.getZatDone(this.__TIME.end) - this.getZatDone(when);
                                      }
 
                                      return this.zatLeft;
                                  },
        'calcZiehIndex'        : function() {
                                      //const __RESTZAT = this.getZatAge(this.__TIME.end) - this.getZatAge() + this.currZAT;
                                      //const __INDEX = parseInt(__RESTZAT / 6 + 1) - 1;  // Lfd. Nummer des Abrechnungsmonats (0-basiert)
 
                                      return (this.warnDraw && this.warnDraw.calcZiehIndex(this.currZAT));
                                  },
        'isZiehAufstieg'        : function() {
                                      return (this.warnDrawAufstieg && this.warnDrawAufstieg.isZiehAufstieg());
                                  },
        'getAge'                : function(when = this.__TIME.now) {
                                      if (this.mwFormel === this.__MWFORMEL.alt) {
                                          return (when === this.__TIME.end) ? 18 : this.age;
                                      } else {  // Geburtstage ab Saison 10...
                                          return (13.00 + this.getZatAge(when) / 72);
                                      }
                                  },
        'getTrainiert'          : function(recalc = false) {
                                      if (recalc || (this.trainiert === undefined)) {
                                          this.trainiert = this.getTrainableSkills();
                                      }
 
                                      return this.trainiert;
                                  },
        'getAufwertungsSchnitt' : function() {
                                      const __ZATDONE = this.getZatDone();
 
                                      if (__ZATDONE) {
                                          return parseFloat(this.getTrainiert() / __ZATDONE);
                                      } else {
                                          // Je nach Talentklasse mittlerer Aufwertungsschnitt aller Talente der Klasse
                                          // (gewichtet nach Verteilung der Talentstufen in dieser Talentklasse)
                                          return (1 + (this.talent / 3.6)) * (this.donation / 10000);
                                      }
                                  },
        'getPos'                : function(idx = 0) {
                                      const __IDXOFFSET = 1;
 
                                      switch (idx) {
                                      case -1 : return (this.bestPos = this.positions[this.isGoalie ? 5 : 0][0]);
                                      case  0 : return this.bestPos;
                                      default : return this.positions[idx - __IDXOFFSET][0];
                                      }
                                  },
        'getPosPercent'        : function(idx = 0) {
                                      const __IDXOFFSET = 1;
                                      const __OPTI = this.positions[this.isGoalie ? 5 : 0][1];
                                      let optiSec = __OPTI;
 
                                      switch (idx) {
                                      case -1 : break;  // __OPTI
                                      case  0 : optiSec = (this.isGoalie ? 0 : this.positions[1][1]);  // Backup-Wert (TOR: keiner)
                                                break;
                                      default : optiSec = this.positions[idx - __IDXOFFSET][1];
                                      }
 
                                      return parseFloat(100 * optiSec / __OPTI);
                                  },
        'getTalent'            : function() {
                                      return (this.talent < 0) ? 'wenig' : (this.talent > 0) ? 'hoch' : 'normal';
                                  },
        'getAufwert'            : function() {
                                      return this.aufwert;
                                  },
        'boldPriSkillNames'    : function(text) {
                                      const __PRISKILLNAMES = this.getPriSkillNames();
 
                                      return (! text) ? text : text.replace(/\w+/g, function(name) {
                                                                                        return ((~ __PRISKILLNAMES.indexOf(name)) ? '<b>' + name + '</b>' : name);
                                                                                    });
                                  },
        'getPriSkillNames'      : function(pos = undefined) {
                                      return getSkillNameArray(getIdxPriSkills(pos ? pos : this.getPos()), this.isGoalie);
                                  },
        'getSkillSum'          : function(when = this.__TIME.now, idxSkills = undefined, restRate = 15) {
                                      let cachedItem;
 
                                      if (idxSkills === undefined) {  // Gesamtsumme ueber alle Skills wird gecached...
                                          cachedItem = ((when === this.__TIME.end) ? 'skillSumEnd' : 'skillSum');
 
                                          const __CACHED = this[cachedItem];
 
                                          if (__CACHED !== undefined) {
                                              return __CACHED;
                                          }
 
                                          idxSkills = getIdxAllSkills();
                                      }
 
                                      const __SKILLS = ((when === this.__TIME.end) ? this.skillsEnd : this.skills);
                                      let sumSkills = ((when === this.__TIME.end) ? (restRate / 15) * this.restEnd : 0);
 
                                      if (__SKILLS) {
                                          for (let idx of idxSkills) {
                                              sumSkills += __SKILLS[idx];
                                          }
                                      }
 
                                      if (cachedItem !== undefined) {
                                          this[cachedItem] = sumSkills;
                                      }
 
                                      return sumSkills;
                                  },
        'getSkill'              : function(when = this.__TIME.now) {
                                      return this.getSkillSum(when) / 17;
                                  },
        'getOpti'              : function(pos, when = this.__TIME.now) {
                                      const __SUMALLSKILLS = this.getSkillSum(when);
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);
/*if (this.zatGeb === 24) {
    console.error("__OVERFLOW = " + __OVERFLOW);
    console.error("__SUMALLSKILLS = " + __SUMALLSKILLS);
    console.error("__SUMPRISKILLS = " + __SUMPRISKILLS);
    console.error("getOpti(" + pos + ") = " + ((4 * (__SUMPRISKILLS - __OVERFLOW) + __SUMALLSKILLS) / 27));
}*/
                                      return (4 * (__SUMPRISKILLS - __OVERFLOW) + __SUMALLSKILLS) / 27;
                                  },
        'getPrios'              : function(pos, when = this.__TIME.now) {
                                      return Math.min(this.__MAXPRISKILLS, this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4)) / 4;
                                  },
        'getPriPercent'        : function(pos, when = this.__TIME.now) {
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __SUMSECSKILLS = this.getSkillSum(when, getIdxSecSkills(pos), 7);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);
 
                                      return (100 * (__SUMPRISKILLS - __OVERFLOW)) / (__SUMPRISKILLS + __SUMSECSKILLS);
                                  },
        'getSecPercent'        : function(pos, when = this.__TIME.now) {
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __SUMSECSKILLS = this.getSkillSum(when, getIdxSecSkills(pos), 7);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);
 
                                      return (100 * (__SUMSECSKILLS + __OVERFLOW)) / (__SUMPRISKILLS + __SUMSECSKILLS);
                                  },
        'getTrainableSkills'    : function(when = this.__TIME.now) {
                                      return this.getSkillSum(when, getIdxTrainableSkills());
                                  },
        'getFixSkills'          : function() {
                                      return this.getSkillSum(this.__TIME.now, getIdxFixSkills());
                                  },
        'getMarketValue'        : function(pos, when = this.__TIME.now) {
                                      const __AGE = this.getAge(when);
 
                                      if (this.mwFormel === this.__MWFORMEL.alt) {
                                          return Math.round(Math.pow((1 + this.getSkill(when)/100) * (1 + this.getOpti(pos, when)/100) * (2 - __AGE/100), 10) * 2);    // Alte Formel bis Saison 9
                                      } else {  // MW-Formel ab Saison 10...
                                          const __MW5TF = 1.00;  // Zwischen 0.97 und 1.03
 
                                          return Math.round(Math.pow(1 + this.getSkill(when)/100, 5.65) * Math.pow(1 + this.getOpti(pos, when)/100, 8.1) * Math.pow(1 + (100 - __AGE)/49, 10) * __MW5TF);
                                      }
                                  },
        'getFingerPrint'        : function() {
                                      // Jeweils gleichbreite Werte: (Alter/Geb.=>Monat), Land, Talent ('-', '=', '+')...
                                      const __BASEPART = padNumber(this.birth / 6, 3) + padLeft(this.land, -3);
                                      const __TALENT = '-=+'[this.talent + 1];
 
                                      if (this.skills === undefined) {
                                          return __BASEPART + getValue(__TALENT, "");
                                      } else {
                                          const __SKILLS = this.skills;
                                          const __FIXSKILLS = getIdxFixSkills().slice(-4);  // ohne die Nullen aus FUQ und ERF
                                          const __FIXSKILLSTR = __FIXSKILLS.map(function(idx) {
                                                                                    return padNumber(__SKILLS[idx], -2);
                                                                                }).join("");
 
                                          // Jeweils gleichbreite Werte: Zusaetzlich vier der sechs Fixskills...
                                          return (__BASEPART + getValue(__TALENT, '?') + __FIXSKILLSTR);
                                      }
                                  },
        'isFingerPrint'        : function(fpA, fpB) {
                                      if (fpA && fpB) {
                                          if (fpA === fpB) {
                                              return true;  // voellig identisch
                                          } else if (this.isBaseFingerPrint(fpA, fpB)) {
                                              return 1;  // schwaches true
                                          }
                                      }
 
                                      return false;
                                  },
        'isBaseFingerPrint'    : function(fpA, fpB) {
                                      if (fpA && fpB) {
                                          if (this.getBaseFingerPrint(fpA) === this.getBaseFingerPrint(fpB)) {
                                              // Base ist identisch...
                                              if ((getValue(fpA[6], '?') === '?') || (getValue(fpB[6], '?') === '?') || (fpA[6] === fpB[6])) {
                                                  // ... und auch das Talent-Zeichen ist leer oder '?'...
                                                  return true;
                                              }
                                          }
                                      }
 
                                      return false;
                                  },
        'getBaseFingerPrint'    : function(fingerprint) {
                                      return (fingerprint ? fingerprint.slice(0, 6) : undefined);
                                  },
        'getCatFromFingerPrint' : function(fingerprint) {
                                      return (fingerprint ? floorValue((fingerprint.slice(0, 3) - 1) / 12) : undefined);
                                  },
        'getCat'                : function() {
                                      return (this.birth ? floorValue((this.birth - 1) / 72) : undefined);
                                  },
        'findInFingerPrints'    : function(fingerprints) {
                                      const __MYFINGERPRINT = this.getFingerPrint();  // ggfs. unvollstaendiger Fingerprint
                                      const __MYCAT = this.getCat();
                                      const __RET = [];
 
                                      if (__MYCAT !== undefined) {
                                          for (let id in fingerprints) {
                                              const __CAT = this.getCatFromFingerPrint(id);
 
                                              if (__CAT === __MYCAT) {
                                                  if (this.isFingerPrint(id, __MYFINGERPRINT)) {
                                                      __RET.push(id);
                                                      break;  // erster Treffer zaehlt
                                                  }
                                              }
                                          }
                                      }
 
                                      return ((__RET.length === 1) ? __RET[0] : undefined);
                                  }
    });
 
// Klasse WarnDrawPlayer *****************************************************************
 
function WarnDrawPlayer(player, alertColor) {
    'use strict';
 
    this.player = player;
 
    if (this.player !== undefined) {
        // Default Warnlevel...
        this.setZatLeft(player.getZatLeft());
        this.currZAT = player.currZAT;
        this.setWarn(true, true, true);
        this.colAlert = alertColor || this.alertColor();
    } else {
        // Kein Warnlevel...
        this.setZatLeft(undefined);
        this.currZAT = undefined;
        this.setWarn(false, false, false);
        this.colAlert = undefined;
    }
}
 
Class.define(WarnDrawPlayer, Object, {
        '__MONATEBISABR'    : 1,
        '__ZATWARNVORLAUF'  : 1,
        '__ZATMONATVORLAUF' : 6,
        'setZatLeft'        : function(zatLeft) {
                                  this.zatLeft = zatLeft;
                              },
        'setWarn'          : function(warn, warnMonth, warnAufstieg) {
                                  this.warn = (this.aufstieg ? warnAufstieg : warn);
                                  this.warnMonth = warnMonth;
                              },
        'alertColor'        : function() {
                                  return getColor('STU');  // rot
                              },
        'getColor'          : function(color) {
                                  return ((this.mustDraw() && this.colAlert) ? this.colAlert : color);
                              },
        'calcZiehIndex'    : function(currZAT) {
                                  const __RESTZAT = this.zatLeft + currZAT;
                                  const __INDEX = parseInt(__RESTZAT / 6 + 1) - this.__MONATEBISABR;  // Lfd. Nummer des Abrechnungsmonats (0-basiert)
 
                                  return __INDEX;
                              },
        'isZiehAufstieg'    : function() {
                                  return this.aufstieg;
                              },
        'setAufstieg'      : function() {
                                  this.aufstieg = true;
 
                                  if (this.isZiehAufstieg()) {
                                      this.setZatLeft(72 - this.currZAT - this.__ZATWARNVORLAUF);
                                  }
 
                                  return this.zatLeft;
                              },
        'mustDraw'          : function() {
                                  return ((this.warn || this.warnMonth) && (this.zatLeft < this.__ZATWARNVORLAUF));
                              },
        'monthDraw'        : function() {
                                  return (this.mustDraw() || (this.warn && (this.aufstieg || this.warnMonth) && (this.zatLeft < this.__ZATMONATVORLAUF)));  // Abrechnungszeitraum vor dem letztmoeglichen Ziehen...
                              }
    });
 
const __NOWARNDRAW = new WarnDrawPlayer(undefined, undefined);  // inaktives Objekt
 
// Klasse WarnDrawMessage *****************************************************************
 
function WarnDrawMessage(optSet, currZAT) {
    'use strict';
 
    this.optSet = optSet;
 
    this.warn = getOptValue(this.optSet.zeigeWarnung, true);
    this.warnMonth = getOptValue(this.optSet.zeigeWarnungMonat, true);
    this.warnHome = getOptValue(this.optSet.zeigeWarnungHome, true);
    this.warnDialog = getOptValue(this.optSet.zeigeWarnungDialog, false);
    this.warnAufstieg = getOptValue(this.optSet.zeigeWarnungAufstieg, true);
    this.warnLegende = getOptValue(this.optSet.zeigeWarnungLegende, true);
 
    this.out = {
                  'supertag' : true,
                  'top'      : true,
                  'link'    : true,
                  'label'    : true,
                  'bottom'  : true
              };
 
    this.setOptionHome();
 
    this.startMessage(currZAT);
}
 
Class.define(WarnDrawMessage, Object, {
        '__ZATWARNVORLAUF'  : 1,
        '__ZATMONATVORLAUF' : 6,
        'startMessage'      : function(currZAT) {
                                  this.setZat(currZAT);
                                  this.createMessage();
                              },
        'setZat'            : function(currZAT) {
                                  this.currZAT = currZAT;
 
                                  if (currZAT === undefined) {
                                      this.abrZAT = undefined;
                                      this.rest  = undefined;
                                      this.anzahl = undefined;
                                  } else {
                                      this.configureZat();
                                  }
                              },
        'setOptionHome'    : function() {
                                  this.warnOption = this.hasHome();
                              },
        'setOptionLegende'  : function() {
                                  this.warnOption = this.hasLegende();
                              },
        'configureZat'      : function() {
                                  const __ZIEHANZAHL = getOptValue(this.optSet.ziehAnz, []);
                                  const __INDEX = parseInt(this.currZAT / 6);
 
                                  this.abrZAT = (__INDEX + 1) * 6;
                                  this.rest  = 5 - (this.currZAT % 6);
                                  this.anzahl = __ZIEHANZAHL[__INDEX];
                              },
        'getTextMessage'    : function() {
                                  return "ZAT " + this.abrZAT + ' ' + ((this.anzahl > 1) ? "m\xFCssen " + this.anzahl : "muss einer") +
                                        " deiner Jugendspieler in das Profiteam \xFCbernommen werden, ansonsten verschwinde" + ((this.anzahl > 1) ? "n sie" : "t er") + '!';
                              },
        'createMessage'    : function() {
                                  this.label = undefined;
                                  this.when = undefined;
                                  this.text = undefined;
 
                                  if (this.hasHome() || this.hasLegende() || this.hasDialog()) {
                                      if (this.anzahl > 0) {
                                          this.text = this.getTextMessage();
 
                                          if (this.warnMonth && (this.rest > 0)) {
                                              this.label = "Warnung";
                                              this.when = "Bis zur n\xE4chsten Abrechnung am ";
                                          } else if ((this.warn || this.warnMonth) && (this.rest === 0)) {
                                              this.label = "LETZTE WARNUNG VOR DER ABRECHNUNG";
                                              this.when = "Bis zum n\xE4chsten ";
                                          }
                                      }
                                  }
                              },
        'hasMessage'        : function() {
                                  return !! this.when;
                              },
        'hasHome'          : function() {
                                  return this.warnHome;
                              },
        'hasLegende'        : function() {
                                  return this.warnLegende;
                              },
        'hasOption'        : function() {
                                  return this.warnOption;
                              },
        'hasDialog'        : function() {
                                  return this.warnDialog;
                              },
        'showMessage'      : function(anchor, tag, appendFind = true) {  // appendFind: true = append, false = insertBefore, "..." search string = insert at find position
                                  let ret = (anchor || { }).innerHTML;
 
                                  if (this.hasMessage()) {
                                      if (this.hasOption()) {
                                          const __OLDHTML = ret;
                                          const __HTML = this.getHTML(tag);
 
                                          if ((typeof appendFind) === 'string') {
                                              const __INDEX = __OLDHTML.indexOf(appendFind);
                                              const __POS = (~ __INDEX) ? __INDEX : __OLDHTML.length;
 
                                              ret = __OLDHTML.substring(0, __POS) + __HTML + __OLDHTML.substring(__POS);
                                          } else if (appendFind) {
                                              ret = __OLDHTML + __HTML;
                                          } else {
                                              ret = __HTML + __OLDHTML;
                                          }
 
                                          anchor.innerHTML = ret;
                                      }
                                  }
 
                                  return ret;
                              },
        'showDialog'        : function(dlgFun) {
                                  if (this.hasMessage()) {
                                      if (this.hasDialog() && (this.rest === 0)) {
                                          dlgFun(this.label, this.when + this.text);
                                      }
                                  }
                              },
        'tagText'          : function(tag, text) {
                                  return ((tag !== undefined) ? this.getOpeningTag(tag) + text + this.getClosingTag(tag) : text);
                              },
        'tagParagraph'      : function(tag, text) {
                                  return this.tagText(tag, this.tagText(this.getSubTag(tag), text));
                              },
        'getSubTag'        : function(tag) {
                                  return ((tag === 'tr') ? 'td' + this.getColorTD() : ((tag === 'p') ? this.getColorTag() : undefined));
                              },
        'getSuperTag'      : function(tag) {
                                  return ((tag === 'p') ? 'div' : undefined);
                              },
        'getOpeningTag'    : function(tag) {
                                  return '<' + tag + '>';
                              },
        'getClosingTag'    : function(tag) {
                                  const __INDEX1 = (tag ? tag.indexOf(' ') : -1);
                                  const __INDEX2 = (tag ? tag.indexOf('=') : -1);
                                  const __INDEX = ((~ __INDEX1) && (~ __INDEX2)) ? Math.min(__INDEX1, __INDEX2) : Math.max(__INDEX1, __INDEX2);
                                  const __TAGNAME = ((~ __INDEX) ? tag.substring(0, __INDEX) : tag);
 
                                  return "</" + __TAGNAME + '>';
                              },
        'getLink'          : function() {
                                  return './ju.php';
                              },
        'getTopHTML'        : function(tag) {
                                  return this.tagParagraph(tag, "&nbsp;");
                              },
        'getBottomHTML'    : function(tag) {
                                  return this.tagParagraph(tag, "&nbsp;");
                              },
        'getColorTag'      : function() {
                                  return "color='red'";  // rot
                              },
        'getColorTD'        : function() {
                                  return " class='STU'";  // rot
                              },
        'getHTML'          : function(tag = 'p') {
                                  return this.tagParagraph((this.out.supertag ? this.getSuperTag(tag) : undefined), (this.out.top ? this.getTopHTML(tag) : "") +
                                        this.tagParagraph(tag, this.tagText('b', this.tagText((this.out.link ? "a href='" + this.getLink() + "'" : undefined),
                                        (this.out.label ? this.label + ": " : "") + this.when + this.text))) + (this.out.bottom ? this.getBottomHTML(tag) : ""));
                              }
    });
 
Object.defineProperty(WarnDrawMessage.prototype, 'innerHTML', {
        get : function() {
                  return this.getHTML('p');
              }
    });
 
// Klasse WarnDrawMessageAufstieg *****************************************************************
 
function WarnDrawMessageAufstieg(optSet, currZAT) {
    'use strict';
 
    WarnDrawMessage.call(this, optSet, currZAT);
 
    this.out.top = false;  // kein Vorschub vor der Zeile
 
    this.warn = (this.warn && this.warnAufstieg);  // kann man ausschalten
    this.startMessage(currZAT);  // 2. Aufruf (zur Korrektur)
}
 
Class.define(WarnDrawMessageAufstieg, WarnDrawMessage, {
        'configureZat'      : function() {
                                  const __ZIEHANZAUFSTIEG = getOptValue(this.optSet.ziehAnzAufstieg, 0);
                                  const __INDEX = parseInt(this.currZAT / 6);
 
                                  this.abrZAT = (__INDEX + 1) * 6;
                                  this.rest  = 5 - (this.currZAT % 6);
                                  this.anzahl = ((this.currZAT + this.__ZATMONATVORLAUF > 72 - this.__ZATWARNVORLAUF) ? __ZIEHANZAUFSTIEG : 0);
 
                                  this.warnDialog = false;    // kein Dialog fuer Aufstiegswarnung
                                  this.warnMonth = this.warn;  // nur im letzten Monat der Saison!
                              },
        'getTextMessage'    : function() {
                                  return "ZAT " + this.abrZAT + " ist im Falle eines Aufstiegs f\xFCr " + ((this.anzahl > 1) ? "" + this.anzahl : "einen") +
                                        " deiner Jugendspieler m\xF6glicherweise die letzte Chance, " + ((this.anzahl > 1) ? " diese noch vor ihrem" : "ihn noch vor seinem") +
                                        " Geburtstag in der n\xE4chsten Saison in das Profiteam zu \xFCbernehmen!";
                              },
        'getColorTag'      : function() {
                                  return "color='magenta'";  // magenta
                              },
        'getColorTD'        : function() {
                                  return " class='OMI'";  // magenta
                              }
    });
 
// Ende Hilfs-Klassen *****************************************************************
 
// Funktionen fuer die HTML-Seite *******************************************************
 
// Liest eine Zahl aus der Spalte einer Zeile der Tabelle aus (z.B. Alter, Geburtsdatum)
// cells: Die Zellen einer Zeile
// colIdxInt: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Zahl (-1 fuer "keine Zahl", undefined fuer "nicht gefunden")
function getIntFromHTML(cells, colIdxInt) {
    const __CELL = getValue(cells[colIdxInt], { });
    const __TEXT = __CELL.textContent;
 
    if (__TEXT !== undefined) {
        try {
            const __VALUE = parseInt(__TEXT, 10);
 
            if (! isNaN(__VALUE)) {
                return __VALUE;
            }
        } catch (ex) { }
 
        return -1;
    }
 
    return undefined;
}
 
// Liest eine Dezimalzahl aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxInt: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Dezimalzahl (undefined fuer "keine Zahl" oder "nicht gefunden")
function getFloatFromHTML(cells, colIdxFloat) {
    const __CELL = getValue(cells[colIdxFloat], { });
    const __TEXT = __CELL.textContent;
 
    if (__TEXT !== undefined) {
        try {
            return parseFloat(__TEXT);
        } catch (ex) { }
    }
 
    return undefined;
}
 
// Liest einen String aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Spalteneintrag als String ("" fuer "nicht gefunden")
function getStringFromHTML(cells, colIdxStr) {
    const __CELL = getValue(cells[colIdxStr], { });
    const __TEXT = __CELL.textContent;
 
    return getValue(__TEXT.toString(), "");
}
 
// Liest ein erstes Element aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Element (null fuer "nicht gefunden")
function getElementFromHTML(cells, colIdxStr) {
    const __CELL = getValue(cells[colIdxStr], { });
 
    return __CELL.firstElementChild;
}
 
// Liest die Talentklasse ("wenig", "normal", "hoch") aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
function getTalentFromHTML(cells, colIdxTal) {
    const __TEXT = getStringFromHTML(cells, colIdxTal);
 
    return parseInt((__TEXT === 'wenig') ? -1 : (__TEXT === 'hoch') ? +1 : 0, 10);
}
 
// Liest die Einzelskills aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdx: Liste von Spaltenindices der gesuchten Werte mit den Eintraegen
// 'Einz' (erste Spalte) und 'Zus' (Spalte hinter dem letzten Eintrag)
// return Skills als Array von Zahlen
function getSkillsFromHTML(cells, colIdx) {
    const __RESULT = [];
 
    for (let i = colIdx.Einz; i < colIdx.Zus; i++) {
        __RESULT[i - colIdx.Einz] = getIntFromHTML(cells, i);
    }
 
    return __RESULT;
}
 
// Liest aus, ob der Spieler Torwart oder Feldspieler ist
// cells: Die Zellen einer Zeile
// colIdxClass: Spaltenindex einer fuer TOR eingefaerbten Zelle
// return Angabe, der Spieler Torwart oder Feldspieler ist
function isGoalieFromHTML(cells, colIdxClass) {
    return (cells[colIdxClass].className === 'TOR');
}
 
// Liest einen String aus der Spalte einer Zeile der Tabelle aus, nachdem dieser konvertiert wurde
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// convertFun: Funktion, die den Wert konvertiert
// return Spalteneintrag als String ("" fuer "nicht gefunden")
function convertStringFromHTML(cells, colIdxStr, convertFun = sameValue) {
    const __CELL = getValue(cells[colIdxStr], { });
    const __TEXT = convertFun(__CELL.textContent, __CELL, colIdxStr, 0);
 
    if (__TEXT !== undefined) {
        __CELL.innerHTML = __TEXT;
    }
 
    return getValue(__TEXT.toString(), "");
}
 
// Liest ein Array von String-Werten aus den Spalten ab einer Zeile der Tabelle aus, nachdem diese konvertiert wurden
// cells: Die Zellen einer Zeile
// colIdxArr: Erster Spaltenindex der gesuchten Werte
// arrOrLength: Entweder ein Datenarray zum Fuellen oder die Anzahl der zu lesenden Werte
// convertFun: Funktion, die die Werte konvertiert
// return Array mit Spalteneintraegen als String ("" fuer "nicht gefunden")
function convertArrayFromHTML(cells, colIdxArr, arrOrLength = 1, convertFun = sameValue) {
    const __ARR = ((typeof arrOrLength === 'number') ? { } : arrOrLength);
    const __LENGTH = getValue(__ARR.length, arrOrLength);
    const __RET = [];
 
    for (let index = 0, colIdx = colIdxArr; index < __LENGTH; index++, colIdx++) {
        const __CELL = getValue(cells[colIdx], { });
        const __TEXT = convertFun(getValue(__ARR[index], __CELL.textContent), __CELL, colIdx, index);
 
        if (__TEXT !== undefined) {
            __CELL.innerHTML = __TEXT;
        }
 
        __RET.push(getValue(__TEXT, "").toString());
    }
 
    return __RET;
}
 
// Konvertiert den Aufwertungstext einer Zelle auf der Jugend-Teamuebersicht
// value: Der Inhalt dieser Zeile ("+1 SKI +1 OPT" bzw. "+2 SKI)
// cell: Zelle, in der der Text stand (optional)
// return Der konvertierte String ("SKI OPT" bzw. "SKI SKI")
function convertAufwertung(value, cell = undefined) {
    if (value !== undefined) {
        value = value.replace(/\+2 (\w+)/, "$1 $1").replace(/\+1 /g, "");
 
        if (cell) {
            if (cell.className === 'TOR') {
                value = convertGoalieSkill(value);
            }
 
            cell.align = 'left';
        }
    }
 
    return value;
}
 
// Konvertiert die allgemeinen Skills in die eines Torwarts
// value: Ein Text, der die Skillnamen enthaelt
// return Der konvertierte String mit Aenderungen (z.B. "FAN" statt "KOB") oder unveraendert
function convertGoalieSkill(value) {
    if (value !== undefined) {
        value = value.replace(/\w+/g, getGoalieSkill);
    }
 
    return value;
}
 
// Konvertiert einen Aufwertungstext fuer einen Skillnamen in den fuer einen Torwart
// name: Allgemeiner Skillname (abgeleitet von den Feldspielern)
// return Der konvertierte String (z.B. "FAN" statt "KOB") oder unveraendert
function getGoalieSkill(name) {
    const __GOALIESKILLS = {
                              'SCH' : 'ABS',
                              'BAK' : 'STS',
                              'KOB' : 'FAN',
                              'ZWK' : 'STB',
                              'DEC' : 'SPL',
                              'GES' : 'REF'
                          };
 
    return getValue(__GOALIESKILLS[name], name);
}
 
// Liest die Aufwertungen eines Spielers aus und konvertiert je nachdem, ob der Spieler Torwart oder Feldspieler ist
// cells: Die Zellen einer Zeile
// colIdxAuf: Spaltenindex der gesuchten Aufwertungen
// shortForm: true = abgekuerzt, false = Originalform
// return Konvertierte Aufwertungen (kurze oder lange Form, aber in jedem Fall fuer Torwart konvertiert)
function getAufwertFromHTML(cells, colIdxAuf, shortForm = true) {
    const __ISGOALIE = isGoalieFromHTML(cells, colIdxAuf);
 
    return convertStringFromHTML(cells, colIdxAuf, (shortForm ? convertAufwertung : __ISGOALIE ? convertGoalieSkill : undefined));
}
 
// Identitaetsfunktion. Konvertiert nichts, sondern liefert einfach den Wert zurueck
// value: Der uebergebene Wert
// return Derselbe Wert
function sameValue(value) {
    return value;
}
 
// Existenzfunktion. Liefert zurueck, ob ein Wert belegt ist
// value: Der uebergebene Wert
// return Angabe ob Wert belegt ist
function existValue(value) {
    return !! value;
}
 
// Liefert den ganzzeiligen Anteil einer Zahl zurueck, indem alles hinter einem Punkt abgeschnitten wird
// value: Eine uebergebene Dezimalzahl
// return Der ganzzeilige Anteil dieser Zahl
function floorValue(value, dot = '.') {
    if ((value === 0) || (value && isFinite(value))) {
        const __VALUE = value.toString();
        const __INDEXDOT = (__VALUE ? __VALUE.indexOf(dot) : -1);
 
        return Number((~ __INDEXDOT) ? __VALUE.substring(0, __INDEXDOT) : __VALUE);
    } else {
        return value;
    }
}
 
// Liefert einen rechtsbuendigen Text zurueck, der links aufgefuellt wird
// value: Ein uebergebener Wert
// size: Zielbreite (clipping fuer < 0: Abschneiden, falls zu lang)
// char: Zeichen zum Auffuellen
// return Ein String, der mindestens |size| lang ist (oder genau, falls size < 0, also clipping)
function padLeft(value, size = 4, char = ' ') {
    const __SIZE = Math.abs(size);
    const __CLIP = (size < 0);
    const __VALUE = (value ? value.toString() : "");
    let i = __VALUE.length;
    let str = "";
 
    while (i < __SIZE) {
        str += char;
        i += char.length;
    }
    str = ((i > __SIZE) ? str.slice(0, __SIZE - __VALUE.length - 1) : str) + __VALUE;
 
    return (__CLIP ? str.slice(size) : str);
}
 
// Liefert eine rechtsbuendigen Zahl zurueck, der links (mit Nullen) aufgefuellt wird
// value: Eine uebergebene Zahl
// size: Zielbreite (Default: 2)
// char: Zeichen zum Auffuellen (Default: '0')
// forceClip: Abschneiden erzwingen, falls zu lang?
// return Eine Zahl als String, der mindestens 'size' lang ist (oder genau, falls size < 0, also clipping)
function padNumber(value, size = 2, char = '0') {
    if ((value === 0) || (value && isFinite(value))) {
        return padLeft(value, size, char);
    } else {
        return value;
    }
}
 
// Hilfsfunktionen **********************************************************************
 
// Sortiert das Positionsfeld per BubbleSort
function sortPositionArray(array) {
    const __TEMP = [];
    let transposed = true;
    // TOR soll immer die letzte Position im Feld sein, deshalb - 1
    let length = array.length - 1;
 
    while (transposed && (length > 1)) {
        transposed = false;
        for (let i = 0; i < length - 1; i++) {
            // Vergleich Opti-Werte:
            if (array[i][1] < array[i + 1][1]) {
                // vertauschen
                __TEMP[0] = array[i][0];
                __TEMP[1] = array[i][1];
                array[i][0] = array[i + 1][0];
                array[i][1] = array[i + 1][1];
                array[i + 1][0] = __TEMP[0];
                array[i + 1][1] = __TEMP[1];
                transposed = true;
            }
        }
        length--;
    }
}
 
// Schaut nach, ob der uebergebene Index zu einem trainierbaren Skill gehoert
// Die Indizes gehen von 0 (SCH) bis 16 (EIN)
function isTrainableSkill(idx) {
    const __TRAINABLESKILLS = getIdxTrainableSkills();
    const __IDX = parseInt(idx, 10);
    let result = false;
 
    for (let idxTrainable of __TRAINABLESKILLS) {
        if (__IDX === idxTrainable) {
            result = true;
            break;
        }
    }
 
    return result;
}
 
// Gibt alle Skill-Namen zurueck
function getAllSkillNames(isGoalie = false) {
    if (isGoalie) {
        return [ 'ABS', 'STS', 'FAN', 'STB', 'SPL', 'REF', 'FUQ', 'ERF', 'AGG', 'PAS', 'AUS', 'UEB', 'WID', 'SEL', 'DIS', 'ZUV', 'EIN' ];
    } else {
        return [ 'SCH', 'BAK', 'KOB', 'ZWK', 'DEC', 'GES', 'FUQ', 'ERF', 'AGG', 'PAS', 'AUS', 'UEB', 'WID', 'SEL', 'DIS', 'ZUV', 'EIN' ];
    }
}
 
// Gibt den Skill-Namen zu einem Index zurueck
function getSkillName(idx, isGoalie = false) {
    const __ALLNAMES = getAllSkillNames(isGoalie);
 
    return __ALLNAMES[idx];
}
 
// Gibt den Skill-Namen zu einem Index-Array zurueck
function getSkillNameArray(idxArr, isGoalie = false) {
    return (idxArr ? idxArr.map(function(item) {
                                    return getSkillName(item, isGoalie);
                                }) : idxArr);
}
 
// Gibt die Indizes aller Skills zurueck
function getIdxAllSkills() {
    return [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
}
 
// Gibt die Indizes der trainierbaren Skills zurueck
function getIdxTrainableSkills() {
    return [ 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 15 ];
}
 
// Gibt die Indizes der Fixskills zurueck
function getIdxFixSkills() {
    return [ 6, 7, 12, 13, 14, 16 ];
}
 
// Gibt die Indizes der Primaerskills zurueck
function getIdxPriSkills(pos) {
    switch (pos) {
        case 'TOR' : return [ 2, 3, 4, 5 ];
        case 'ABW' : return [ 2, 3, 4, 15 ];
        case 'DMI' : return [ 1, 4, 9, 11 ];
        case 'MIT' : return [ 1, 3, 9, 11 ];
        case 'OMI' : return [ 1, 5, 9, 11 ];
        case 'STU' : return [ 0, 2, 3, 5 ];
        default :    return [];
    }
}
 
// Gibt die Indizes der (trainierbaren) Sekundaerskills zurueck
function getIdxSecSkills(pos) {
    switch (pos) {
        case 'TOR' : return [ 0, 1, 8, 9, 10, 11, 15 ];
        case 'ABW' : return [ 0, 1, 5, 8, 9, 10, 11 ];
        case 'DMI' : return [ 0, 2, 3, 5, 8, 10, 15 ];
        case 'MIT' : return [ 0, 2, 4, 5, 8, 10, 15 ];
        case 'OMI' : return [ 0, 2, 3, 4, 8, 10, 15 ];
        case 'STU' : return [ 1, 4, 8, 9, 10, 11, 15 ];
        default :    return [];
    }
}
 
// Gibt die zur Position gehoerige Farbe zurueck
function getColor(pos) {
    switch (pos) {
        case 'TOR' : return "#FFFF00";
        case 'ABW' : return "#00FF00";
        case 'DMI' : return "#3366FF";
        case 'MIT' : return "#66FFFF";
        case 'OMI' : return "#FF66FF";
        case 'STU' : return "#FF0000";
        case 'LEI' : return "#FFFFFF";
        case "" :    return "#111166";  // osBlau
        default :    return "";
    }
}
 
// ==================== Ende Abschnitt genereller Code zur Anzeige der Jugend ====================
 
// ==================== Abschnitt fuer interne IDs auf den Seiten ====================
 
const __GAMETYPENRN = {    // "Blind FSS gesucht!"
        'unbekannt'  : -1,
        'reserviert' :  0,
        'Frei'      :  0,
        'spielfrei'  :  0,
        'Friendly'  :  1,
        'Liga'      :  2,
        'LP'        :  3,
        'OSEQ'      :  4,
        'OSE'        :  5,
        'OSCQ'      :  6,
        'OSC'        :  7,
        'Supercup'  : 10
    };
 
const __GAMETYPEALIASES = {
        'unbekannt'  :  "unbekannt",
        'reserviert' :  undefined,
        'Frei'      :  undefined,
        'spielfrei'  :  "",
        'Friendly'  :  "FSS",
        'Liga'      :  undefined,
        'LP'        :  "Pokal",
        'OSEQ'      :  undefined,
        'OSE'        :  undefined,
        'OSCQ'      :  undefined,
        'OSC'        :  undefined,
        'Supercup'  : "Super"
    };
const __GAMETYPES = reverseMapping(__GAMETYPENRN);
 
const __LIGANRN = {
        'unbekannt'  :  0,
        '1. Liga'    :  1,
        '2. Liga A'  :  2,
        '2. Liga B'  :  3,
        '3. Liga A'  :  4,
        '3. Liga B'  :  5,
        '3. Liga C'  :  6,
        '3. Liga D'  :  7
    };
const __LIGATYPES = reverseMapping(__LIGANRN);
 
const __LANDNRN = {
        'unbekannt'              :  0,
        'Albanien'              :  45,
        'Andorra'                :  95,
        'Armenien'              :  83,
        'Aserbaidschan'          : 104,
        'Belgien'                :  12,
        'Bosnien-Herzegowina'    :  66,
        'Bulgarien'              :  42,
        'D\xE4nemark'            :  8,
        'Deutschland'            :  6,
        'England'                :  1,
        'Estland'                :  57,
        'Far\xF6er'              :  68,
        'Finnland'              :  40,
        'Frankreich'            :  32,
        'Georgien'              :  49,
        'Griechenland'          :  30,
        'Irland'                :  5,
        'Island'                :  29,
        'Israel'                :  23,
        'Italien'                :  10,
        'Kasachstan'            : 105,
        'Kroatien'              :  24,
        'Lettland'              :  97,
        'Liechtenstein'          :  92,
        'Litauen'                :  72,
        'Luxemburg'              :  93,
        'Malta'                  :  69,
        'Mazedonien'            :  86,
        'Moldawien'              :  87,
        'Niederlande'            :  11,
        'Nordirland'            :  4,
        'Norwegen'              :  9,
        '\xD6sterreich'          :  14,
        'Polen'                  :  25,
        'Portugal'              :  17,
        'Rum\xE4nien'            :  28,
        'Russland'              :  19,
        'San Marino'            :  98,
        'Schottland'            :  2,
        'Schweden'              :  27,
        'Schweiz'                :  37,
        'Serbien und Montenegro' :  41,
        'Slowakei'              :  70,
        'Slowenien'              :  21,
        'Spanien'                :  13,
        'Tschechien'            :  18,
        'T\xFCrkei'              :  39,
        'Ukraine'                :  20,
        'Ungarn'                :  26,
        'Wales'                  :  3,
        'Weissrussland'          :  71,
        'Zypern'                :  38
    };
const __LAENDER = reverseMapping(__LANDNRN);
 
const __TLALAND = {
        undefined : 'unbekannt',
        'ALB'    : 'Albanien',
        'AND'    : 'Andorra',
        'ARM'    : 'Armenien',
        'AZE'    : 'Aserbaidschan',
        'BEL'    : 'Belgien',
        'BIH'    : 'Bosnien-Herzegowina',
        'BUL'    : 'Bulgarien',
        'DEN'    : 'D\xE4nemark',
        'GER'    : 'Deutschland',
        'ENG'    : 'England',
        'EST'    : 'Estland',
        'FRO'    : 'Far\xF6er',
        'FIN'    : 'Finnland',
        'FRA'    : 'Frankreich',
        'GEO'    : 'Georgien',
        'GRE'    : 'Griechenland',
        'IRL'    : 'Irland',
        'ISL'    : 'Island',
        'ISR'    : 'Israel',
        'ITA'    : 'Italien',
        'KAZ'    : 'Kasachstan',
        'CRO'    : 'Kroatien',
        'LVA'    : 'Lettland',
        'LIE'    : 'Liechtenstein',
        'LTU'    : 'Litauen',
        'LUX'    : 'Luxemburg',
        'MLT'    : 'Malta',
        'MKD'    : 'Mazedonien',
        'MDA'    : 'Moldawien',
        'NED'    : 'Niederlande',
        'NIR'    : 'Nordirland',
        'NOR'    : 'Norwegen',
        'AUT'    : '\xD6sterreich',
        'POL'    : 'Polen',
        'POR'    : 'Portugal',
        'ROM'    : 'Rum\xE4nien',
        'RUS'    : 'Russland',
        'SMR'    : 'San Marino',
        'SCO'    : 'Schottland',
        'SWE'    : 'Schweden',
        'SUI'    : 'Schweiz',
        'SCG'    : 'Serbien und Montenegro',
        'SVK'    : 'Slowakei',
        'SVN'    : 'Slowenien',
        'ESP'    : 'Spanien',
        'CZE'    : 'Tschechien',
        'TUR'    : 'T\xFCrkei',
        'UKR'    : 'Ukraine',
        'HUN'    : 'Ungarn',
        'WAL'    : 'Wales',
        'BLR'    : 'Weissrussland',
        'CYP'    : 'Zypern'
    };
const __LANDTLAS = reverseMapping(__TLALAND);
 
// ==================== Abschnitt fuer Daten des Spielplans ====================
 
// Gibt die ID fuer den Namen eines Wettbewerbs zurueck
// gameType: Name des Wettbewerbs eines Spiels
// defValue: Default-Wert
// return OS2-ID fuer den Spieltyp (1 bis 7 oder 10), 0 fuer "spielfrei"/"Frei"/"reserviert", -1 fuer ungueltig
function getGameTypeID(gameType, defValue = __GAMETYPENRN.unbekannt) {
    return getValue(__GAMETYPENRN[gameType], defValue);
}
 
// Gibt den Namen eines Wettbewerbs zurueck
// id: OS2-ID des Wettbewerbs eines Spiels (1 bis 7 oder 10), 0 fuer "spielfrei"/"Frei"/"reserviert", -1 fuer ungueltig
// defValue: Default-Wert
// return Spieltyp fuer die uebergebene OS2-ID
function getGameType(id, defValue) {
    return getValue(__GAMETYPES[id], defValue);
}
 
// Gibt den alternativen (Kurznamen) fuer den Namen eines Wettbewerbs zurueck
// gameType: Name des Wettbewerbs eines Spiels
// return Normalerweise den uebergebenen Parameter, in Einzelfaellen eine Kurzversion
function getGameTypeAlias(gameType) {
    return getValue(__GAMETYPEALIASES[gameType], getValue(gameType, __GAMETYPEALIASES.unbekannt));
}
 
// Gibt den Namen des Landes mit dem uebergebenen Kuerzel (TLA) zurueck.
// tla: Kuerzel (TLA) des Landes
// defValue: Default-Wert
// return Name des Landes, 'unbekannt' fuer undefined
function getLandName(tla, defValue = __TLALAND[undefined]) {
    return getValue(__TLALAND[tla], defValue);
}
 
// Gibt die ID des Landes mit dem uebergebenen Namen zurueck.
// land: Name des Landes
// defValue: Default-Wert
// return OS2-ID des Landes, 0 fuer ungueltig
function getLandNr(land, defValue = __LANDNRN.unbekannt) {
    return getValue(__LANDNRN[land], defValue);
}
 
// Gibt die ID der Liga mit dem uebergebenen Namen zurueck.
// land: Name der Liga
// defValue: Default-Wert
// return OS2-ID der Liga, 0 fuer ungueltig
function getLigaNr(liga, defValue = __LIGANRN.unbekannt) {
    return getValue(__LIGANRN[liga], defValue);
}
 
// Kehrt das Mapping eines Objekts um und liefert ein neues Objekt zurueck.
// obj: Objekt mit key => value
// convFun: Konvertierfunktion fuer die Werte
// return Neues Objekt mit value => key (doppelte value-Werte fallen heraus!)
function reverseMapping(obj, convFun) {
    if (! obj) {
        return obj;
    }
 
    const __RET = { };
 
    for (let key in obj) {
        const __VALUE = obj[key];
 
        __RET[__VALUE] = (convFun ? convFun(key) : key);
    }
 
    return __RET;
}
 
// ==================== Abschnitt fuer sonstige Parameter ====================
 
// Formatiert eine Zelle um (mit einfachen Parametern)
// cell: Zu formatierende Zelle
// bold: Inhalt fett darstellen (true = ja, false = nein)
// color: Falls angegeben, die Schriftfarbe
// bgColor: Falls angegeben, die Hintergrundfarbe
// opacity: Falls angegeben, die Opazitaet
// return Die formatierte Zelle
function formatCell(cell, bold = true, color = undefined, bgColor = undefined, opacity = undefined) {
    if (cell) {
        if (bold) {
            cell.style.fontWeight = 'bold';
        }
        if (color) {
            cell.style.color = color;
        }
        if (bgColor) {
            cell.style.backgroundColor = bgColor;
        }
        if (opacity) {
        cell.style.opacity = opacity;
        }
    }
 
    return cell;
}
 
// Ermittelt die auszugewaehlenden Werte eines Selects (Combo-Box) als Array zurueck
// element: 'select'-Element oder dessen Name auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// valFun: Funktion zur Ermittlung des Wertes eines 'option'-Eintrags (getSelectedOptionText, getSelectedValue, ...)
// defValue: Default-Wert, falls nichts selektiert ist
// return Array mit den Options-Werten
function getSelectionArray(element, valType = 'String', valFun = getSelectedValue, defValue = undefined) {
    const __SELECT = ((typeof element) === 'string' ? getValue(document.getElementsByName(element), [])[0] : element);
 
    return (__SELECT ? [].map.call(__SELECT.options, function(option) {
                                                        return this[valType](getValue(valFun(option), defValue));
                                                    }) : undefined);
}
 
// Ermittelt den ausgewaehlten Wert eines Selects (Combo-Box) und gibt diesen zurueck
// element: 'select'-Element oder dessen Name auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// valFun: Funktion zur Ermittlung des Wertes eines 'option'-Eintrags (getSelectedOptionText, getSelectedValue, ...)
// defValue: Default-Wert, falls nichts selektiert ist
// return Ausgewaehlter Wert
function getSelection(element, valType = 'String', valFun = getSelectedOptionText, defValue = undefined) {
    const __SELECT = ((typeof element) === 'string' ? getValue(document.getElementsByName(element), [])[0] : element);
 
    return this[valType](getValue(valFun(__SELECT), defValue));
}
 
// Ermittelt den ausgewaehlten Wert einer Combo-Box und gibt diesen zurueck
// comboBox: Alle 'option'-Eintraege der Combo-Box
// defValue: Default-Wert, falls nichts selektiert ist
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// return Ausgewaehlter Wert
function getSelectionFromComboBox(comboBox, defValue = undefined, valType = 'String') {
    let selection;
 
    for (let i = 0; i < comboBox.length; i++) {
        const __ENTRY = comboBox[i];
 
        if (__ENTRY.outerHTML.match(/selected/)) {
            selection = __ENTRY.textContent;
        }
    }
 
    return this[valType](getValue(selection, defValue));
}
 
// Liefert den Text (textContent) einer selektierten Option
// element: 'select'-Element auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// return Wert der Selektion (textContent)
function getSelectedOptionText(element) {
    const __SELECTEDOPTIONS = getValue(element, { }).selectedOptions;
    const __OPTION = getValue(__SELECTEDOPTIONS, { })[0];
 
    return (__OPTION ? __OPTION.textContent : undefined);
}
 
// Liefert den 'value' einer selektierten Option
// element: 'select'-Element auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// return Wert der Selektion ('value')
function getSelectedValue(element) {
    return getValue(element, { }).value;
}
 
// ==================== Ende Abschnitt fuer sonstige Parameter ====================
 
// ==================== Abschnitt fuer sonstige Parameter des Spielplans ====================
 
const __TEAMSEARCHHAUPT = {  // Parameter zum Team "<b>Willkommen im Managerb&uuml;ro von TEAM</b><br>LIGA LAND<a href=..."
        'Zeile'  : 0,
        'Spalte' : 1,
        'start'  : " von ",
        'middle' : "</b><br>",
        'liga'  : ". Liga",
        'land'  : ' ',
        'end'    : "<a href="
    };
 
const __TEAMSEARCHTEAM = {  // Parameter zum Team "<b>TEAM - LIGA <a href=...>LAND</a></b>"
        'Zeile'  : 0,
        'Spalte' : 0,
        'start'  : "<b>",
        'middle' : " - ",
        'liga'  : ". Liga",
        'land'  : 'target="_blank">',
        'end'    : "</a></b>"
    };
 
// Ermittelt, wie das eigene Team heisst und aus welchem Land bzw. Liga es kommt (zur Unterscheidung von Erst- und Zweitteam)
// cell: Tabellenzelle mit den Parametern zum Team "startTEAMmiddleLIGA...landLANDend", LIGA = "#liga[ (A|B|C|D)]"
// teamSeach: Muster fuer die Suche, die Eintraege fuer 'start', 'middle', 'liga', 'land' und 'end' enthaelt
// return Im Beispiel { 'Team' : "TEAM", 'Liga' : "LIGA", 'Land' : "LAND", 'LdNr' : LAND-NUMMER, 'LgNr' : LIGA-NUMMER },
//        z.B. { 'Team' : "Choromonets Odessa", 'Liga' : "1. Liga", 'Land' : "Ukraine", 'LdNr' : 20, 'LgNr' : 1 }
function getTeamParamsFromTable(table, teamSearch = undefined) {
    const __TEAMSEARCH  = getValue(teamSearch, __TEAMSEARCHHAUPT);
    const __TEAMCELLROW  = getValue(__TEAMSEARCH.Zeile, 0);
    const __TEAMCELLCOL  = getValue(__TEAMSEARCH.Spalte, 0);
    const __TEAMCELLSTR  = (table === undefined) ? "" : table.rows[__TEAMCELLROW].cells[__TEAMCELLCOL].innerHTML;
    const __SEARCHSTART  = __TEAMSEARCH.start;
    const __SEARCHMIDDLE = __TEAMSEARCH.middle;
    const __SEARCHLIGA  = __TEAMSEARCH.liga;
    const __SEARCHLAND  = __TEAMSEARCH.land;
    const __SEARCHEND    = __TEAMSEARCH.end;
    const __INDEXSTART  = __TEAMCELLSTR.indexOf(__SEARCHSTART);
    const __INDEXEND    = __TEAMCELLSTR.indexOf(__SEARCHEND);
 
    let teamParams = __TEAMCELLSTR.substring(__INDEXSTART + __SEARCHSTART.length, __INDEXEND);
    const __INDEXLIGA = teamParams.indexOf(__SEARCHLIGA);
    const __INDEXMIDDLE = teamParams.indexOf(__SEARCHMIDDLE);
 
    let land = ((~ __INDEXLIGA) ? teamParams.substring(__INDEXLIGA + __SEARCHLIGA.length) : undefined);
    const __TEAMNAME = ((~ __INDEXMIDDLE) ? teamParams.substring(0, __INDEXMIDDLE) : undefined);
    let liga = (((~ __INDEXLIGA) && (~ __INDEXMIDDLE)) ? teamParams.substring(__INDEXMIDDLE + __SEARCHMIDDLE.length) : undefined);
 
    if (land !== undefined) {
        if (land.charAt(2) === ' ') {    // Land z.B. hinter "2. Liga A " statt "1. Liga "
            land = land.substr(2);
        }
        if (liga !== undefined) {
            liga = liga.substring(0, liga.length - land.length);
        }
        const __INDEXLAND = land.indexOf(__SEARCHLAND);
        if (~ __INDEXLAND) {
            land = land.substr(__INDEXLAND + __SEARCHLAND.length);
        }
    }
 
    const __TEAM = new Team(__TEAMNAME, land, liga);
 
    return __TEAM;
}
 
// Verarbeitet die URL der Seite und ermittelt die Nummer der gewuenschten Unterseite
// url: Adresse der Seite
// leafs: Liste von Filenamen mit der Default-Seitennummer (falls Query-Parameter nicht gefunden)
// item: Query-Parameter, der die Nummer der Unterseite angibt
// return Parameter aus der URL der Seite als Nummer
function getPageIdFromURL(url, leafs, item = 'page') {
    const __URI = new URI(url);
    const __LEAF = __URI.getLeaf();
 
    for (let leaf in leafs) {
        if (__LEAF === leaf) {
            const __DEFAULT = leafs[leaf];
 
            return getValue(__URI.getQueryPar(item), __DEFAULT);
        }
    }
 
    return -1;
}
 
// Gibt die laufende Nummer des ZATs im Text einer Zelle zurueck
// cell: Tabellenzelle mit der ZAT-Nummer im Text
// return ZAT-Nummer im Text
function getZATNrFromCell(cell) {
    const __TEXT = ((cell === undefined) ? [] : cell.textContent.split(' '));
    let ZATNr = 0;
 
    for (let i = 1; (ZATNr === 0) && (i < __TEXT.length); i++) {
        if (__TEXT[i - 1] === "ZAT") {
            if (__TEXT[i] !== "ist") {
                ZATNr = parseInt(__TEXT[i], 10);
            }
        }
    }
 
    return ZATNr;
}
 
// ==================== Ende Abschnitt fuer sonstige Parameter des Spielplans ====================
 
// ==================== Ende Abschnitt fuer Spielplan und ZATs ====================
 
// ==================== Hauptprogramm ====================
 
// Verarbeitet Ansicht "Haupt" (Managerbuero) zur Ermittlung des aktuellen ZATs
function procHaupt() {
    const __TEAMPARAMS = getTeamParamsFromTable(getTable(1), __TEAMSEARCHHAUPT);  // Link mit Team, Liga, Land...
 
    return buildOptions(__OPTCONFIG, __OPTSET, {
                            'teamParams' : __TEAMPARAMS,
//                            'menuAnchor' : getTable(0, 'div'),
                            'hideMenu'  : true,
                            'showForm'  : {
                                              'zeigeWarnung'        : true,
                                              'zeigeWarnungMonat'    : true,
                                              'zeigeWarnungHome'    : true,
                                              'zeigeWarnungDialog'  : true,
                                              'zeigeWarnungAufstieg' : true,
                                              'zeigeWarnungLegende'  : true,
                                              'ziehAnz'              : true,
                                              'showForm'            : true
                                          }
                        }).then(async optSet => {
            const __ZATCELL = getProp(getProp(getRows(0), 2), 'cells', { })[0];
            const __NEXTZAT = getZATNrFromCell(__ZATCELL);  // "Der naechste ZAT ist ZAT xx und ..."
            const __CURRZAT = __NEXTZAT - 1;
            const __DATAZAT = getOptValue(__OPTSET.datenZat);
 
            // Stand der alten Daten merken...
            setOpt(__OPTSET.oldDatenZat, __DATAZAT, false);
 
            if (__CURRZAT >= 0) {
                __LOG[2]("Aktueller ZAT: " + __CURRZAT);
 
                // Neuen aktuellen ZAT speichern...
                setOpt(__OPTSET.aktuellerZat, __CURRZAT, false);
 
                if (__CURRZAT !== __DATAZAT) {
                    __LOG[2](__LOG.changed(__DATAZAT, __CURRZAT));
 
                    // ... und ZAT-bezogene Daten als veraltet markieren (ausser 'skills', 'positions' und 'ziehAnz')
                    await __TEAMCLASS.deleteOptions({
                                                    'skills'      : true,
                                                    'positions'  : true,
                                                    'datenZat'    : true,
                                                    'oldDatenZat' : true,
                                                    'ziehAnz'    : (__CURRZAT > __DATAZAT)  // nur loeschen, wenn < __DATAZAT
                                                }).catch(defaultCatch);
 
                    // Neuen Daten-ZAT speichern...
                    setOpt(__OPTSET.datenZat, __CURRZAT, false);
                }
            }
 
            const __MSG = new WarnDrawMessage(optSet, __CURRZAT);
            const __MSGAUFSTIEG = new WarnDrawMessageAufstieg(optSet, __CURRZAT);
            const __ANCHOR = getTable(0, 'tbody');
 
            __MSG.showMessage(__ANCHOR, 'tr', true);
            __MSG.showDialog(showAlert);
            __MSGAUFSTIEG.showMessage(__ANCHOR, 'tr', true);
        });
}
 
// Verarbeitet Ansicht "Optionen" zur Ermittlung der Jugendfoerderung
function procOptionen() {
    return buildOptions(__OPTCONFIG, __OPTSET, {
                            'menuAnchor'  : getTable(0, 'div'),
                            'hideMenu'    : true,
                            'getDonation' : true,
                            'showForm'    : {
                                                'foerderung'          : true,
                                                'zeigeWarnung'        : true,
                                                'zeigeWarnungMonat'    : true,
                                                'zeigeWarnungHome'    : true,
                                                'zeigeWarnungDialog'  : true,
                                                'zeigeWarnungAufstieg' : true,
                                                'zeigeWarnungLegende'  : true,
                                                'ziehAnz'              : true,
                                                'showForm'            : true
                                            }
        });
}
 
// Verarbeitet Ansicht "Teamuebersicht"
function procTeamuebersicht() {
    const __ROWOFFSETUPPER = 1;    // Header-Zeile
    const __ROWOFFSETLOWER = 1;    // Ziehen-Button
 
     const __COLUMNINDEX = {
             'Age'  : 0,
             'Geb'  : 1,
             'Flg'  : 2,
             'Land'  : 3,
             'U'    : 4,
             'Skill' : 5,
             'Tal'  : 6,
             'Akt'  : 7,
             'Auf'  : 8,
             'Zus'  : 9
         };
         };
 
 
     if (getElement('transfer') !== undefined) {
     if (getElement('transfer') !== undefined) {
         __LOG[2]("Ziehen-Seite");
         __LOG[2]("Ziehen-Seite");
     } else if (getRows(1) === undefined) {
     } else if (getRows(1) === undefined) {
         __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
         __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
     } else {
     } else {
         buildOptions(__OPTCONFIG, __OPTSET, {
         return buildOptions(__OPTCONFIG, __OPTSET, {
                        'menuAnchor' : getTable(0, "div"),
                                'menuAnchor' : getTable(0, 'div'),
                        'showForm'  : {
                                'showForm'  : {
                                            'kennzeichenEnde'   : true,
                                                  'kennzeichenEnde'     : true,
                                            'shortAufw'         : true,
                                                  'shortAufw'           : true,
                                            'sepStyle'           : true,
                                                  'sepStyle'             : true,
                                            'sepColor'           : true,
                                                  'sepColor'             : true,
                                            'sepWidth'           : true,
                                                  'sepWidth'             : true,
                                            'saison'             : true,
                                                  'saison'               : true,
                                            'aktuellerZat'       : true,
                                                  'aktuellerZat'         : true,
                                            'team'               : true,
                                                  'foerderung'          : true,
                                            'ersetzeAlter'       : true,
                                                  'team'                 : true,
                                            'zeigeAlter'         : true,
                                                  'zeigeJahrgang'        : true,
                                            'zeigeQuote'         : true,
                                                  'zeigeUxx'            : true,
                                            'zeigePosition'     : true,
                                                  'zeigeWarnung'        : true,
                                            'zeigeFixSkills'     : true,
                                                  'zeigeWarnungMonat'    : true,
                                            'zeigeTrainiert'     : true,
                                                  'zeigeWarnungHome'    : true,
                                            'zeigeAnteilPri'     : true,
                                                  'zeigeWarnungDialog'  : true,
                                            'zeigeAnteilSec'     : true,
                                                  'zeigeWarnungAufstieg' : true,
                                            'zeigePrios'         : true,
                                                  'zeigeWarnungLegende'  : true,
                                            'zeigeSkill'         : true,
                                                  'zeigeBalken'          : true,
                                            'anzahlOpti'         : true,
                                                  'absBalken'            : true,
                                            'anzahlMW'           : true,
                                                  'zeigeId'              : true,
                                            'zeigeTrainiertEnde' : true,
                                                  'ersetzeAlter'         : true,
                                            'zeigeAnteilPriEnde' : true,
                                                  'zeigeAlter'           : true,
                                            'zeigeAnteilSecEnde' : true,
                                                  'zeigeQuote'           : true,
                                            'zeigePriosEnde'     : true,
                                                  'zeigePosition'       : true,
                                            'zeigeSkillEnde'     : true,
                                                  'zeigeZatDone'        : true,
                                            'anzahlOptiEnde'     : true,
                                                  'zeigeZatLeft'         : true,
                                            'anzahlMWEnde'       : true,
                                                  'zeigeFixSkills'       : true,
                                            'zatAges'           : true,
                                                  'zeigeTrainiert'       : true,
                                            'trainiert'         : true,
                                                  'zeigeAnteilPri'       : true,
                                            'positions'         : true,
                                                  'zeigeAnteilSec'       : true,
                                            'skills'             : true,
                                                  'zeigePrios'           : true,
                                            'reset'             : true,
                                                  'anzahlOpti'           : true,
                                            'showForm'           : true
                                                  'anzahlMW'             : true,
                                        },
                                                  'zeigeTrainiertEnde'   : true,
                        'formWidth'  : 1
                                                  'zeigeAnteilPriEnde'   : true,
                    });
                                                  'zeigeAnteilSecEnde'   : true,
 
                                                  'zeigePriosEnde'       : true,
        const __ROWS = getRows(1);
                                                  'zeigeSkillEnde'       : true,
        const __HEADERS = __ROWS[0];
                                                  'anzahlOptiEnde'       : true,
        const __TITLECOLOR = getColor("LEI");  // "#FFFFFF"
                                                  'anzahlMWEnde'         : true,
 
                                                  'ziehAnz'              : true,
        const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, true);
                                                  'zatAges'             : true,
        const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, {
                                                  'trainiert'           : true,
                                            'Default'            : true,
                                                  'positions'           : true,
                                            'zeigeGeb'          : false,
                                                  'skills'               : true,
                                            'zeigeSkill'        : false,
                                                  'reset'               : true,
                                            'zeigeTal'          : false,
                                                  'showForm'             : true
                                            'zeigeAufw'          : false
                                              },
                                        });
                                'formWidth'  : 1
 
                            }).then(optSet => {
         __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
                const __ROWS = getRows(1);
 
                const __HEADERS = __ROWS[0];
        for (let i = 0; i < __PLAYERS.length; i++) {
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"
            __COLMAN.addValues(__PLAYERS[i], __ROWS[i + __ROWOFFSETUPPER], __TITLECOLOR);
 
                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 1);
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, {
                                                    'Default'            : true,
                                                    'ersetzeSkills'      : false,
                                                    'zeigeGeb'          : false,
                                                    'zeigeSkill'        : false,
                                                    'zeigeTal'          : false,
                                                    'zeigeAufw'          : false
                                                });
 
                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
 
                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
                    if (__ROWS[i].cells.length > 1) {
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
                    } else {
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }
 
                // Format der Trennlinie zwischen den Jahrgaengen...
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
 
                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
                }
 
                const __CURRZAT = getOptValue(__OPTSET.datenZat);
                const __MSG = new WarnDrawMessage(__OPTSET, __CURRZAT);
                const __MSGAUFSTIEG = new WarnDrawMessageAufstieg(__OPTSET, __CURRZAT);
                const __ANCHOR = getTable(0, 'div');
                const __SEARCH = '<form method="POST">';
 
                // Kompaktere Darstellung und ohne Links...
                __MSG.out.top = false;
                __MSG.out.label = false;
                __MSG.out.link = false;
                __MSG.out.bottom = false;
                __MSGAUFSTIEG.out.label = false;
                __MSGAUFSTIEG.out.link = false;
                __MSGAUFSTIEG.out.bottom = false;
 
                __MSG.setOptionLegende();
                __MSGAUFSTIEG.setOptionLegende();
 
                __MSG.showMessage(__ANCHOR, 'p', __SEARCH);
                __MSGAUFSTIEG.showMessage(__ANCHOR, 'p', __SEARCH);
            });
    }
 
    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}
 
// Verarbeitet Ansicht "Spielereinzelwerte"
function procSpielereinzelwerte() {
    const __ROWOFFSETUPPER = 1;    // Header-Zeile
    const __ROWOFFSETLOWER = 0;
 
    const __COLUMNINDEX = {
            'Flg'  : 0,
            'Land'  : 1,
            'U'    : 2,
            'X'    : 3,
            'Age'  : 4,
            'Einz'  : 5,    // ab hier die Einzelskills
            'SCH'  : 5,
            'ABS'  : 5,    // TOR
            'BAK'  : 6,
            'STS'  : 6,    // TOR
            'KOB'  : 7,
            'FAN'  : 7,    // TOR
            'ZWK'  : 8,
            'STB'  : 8,    // TOR
            'DEC'  : 9,
            'SPL'  : 9,    // TOR
            'GES'  : 10,
            'REF'  : 10,  // TOR
            'FUQ'  : 11,
            'ERF'  : 12,
            'AGG'  : 13,
            'PAS'  : 14,
            'AUS'  : 15,
            'UEB'  : 16,
            'WID'  : 17,
            'SEL'  : 18,
            'DIS'  : 19,
            'ZUV'  : 20,
            'EIN'  : 21,
            'Zus'  : 22    // Zusaetze hinter den Einzelskills
        };
 
    if (getRows(1) === undefined) {
        __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else {
        return buildOptions(__OPTCONFIG, __OPTSET, {
                                'menuAnchor' : getTable(0, 'div'),
                                'hideForm'  : {
                                                  'zeigeWarnung'         : false,
                                                  'zeigeWarnungMonat'    : false,
                                                  'zeigeWarnungHome'    : false,
                                                  'zeigeWarnungDialog'  : false,
                                                  'zeigeWarnungAufstieg' : false,
                                                  'zeigeWarnungLegende'  : false,
                                                  'ziehAnz'              : true,
                                                  'zatAges'              : true,
                                                  'trainiert'            : true,
                                                  'positions'            : true,
                                                  'skills'              : true,
                                                  'shortAufw'            : true
                                              },
                                'formWidth'  : 1
                            }).then(optSet => {
                const __ROWS = getRows(1);
                const __HEADERS = __ROWS[0];
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"
 
                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 2);
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, true);
 
                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
 
                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
                    if (__ROWS[i].cells.length > 1) {
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
                    } else {
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }
 
                // Format der Trennlinie zwischen den Jahrgaengen...
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
 
                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
                }
            });
    }
 
    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}
 
// Verarbeitet Ansicht "Opt. Skill"
function procOptSkill() {
    const __ROWOFFSETUPPER = 1;    // Header-Zeile
    const __ROWOFFSETLOWER = 0;
 
    const __COLUMNINDEX = {
            'Flg'  : 0,
            'Land'  : 1,
            'U'    : 2,
            'Age'  : 3,
            'Skill' : 4,
            'TOR'  : 5,
            'ABW'  : 6,
            'DMI'  : 7,
            'MIT'  : 8,
            'OMI'  : 9,
            'STU'  : 10,
            'Zus'  : 11    // Zusaetze hinter den OptSkills
        };
 
    if (getRows(1) === undefined) {
        __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else {
        return buildOptions(__OPTCONFIG, __OPTSET, {
                                'menuAnchor' : getTable(0, 'div'),
                                'showForm'  : {
                                                  'kennzeichenEnde'      : true,
                                                  'sepStyle'            : true,
                                                  'sepColor'            : true,
                                                  'sepWidth'            : true,
                                                  'saison'              : true,
                                                  'aktuellerZat'        : true,
                                                  'foerderung'          : true,
                                                  'team'                : true,
                                                  'zeigeJahrgang'        : true,
                                                  'zeigeUxx'            : true,
                                                  'zeigeWarnung'        : false,
                                                  'zeigeWarnungMonat'    : false,
                                                  'zeigeWarnungHome'    : false,
                                                  'zeigeWarnungDialog'  : false,
                                                  'zeigeWarnungAufstieg' : false,
                                                  'zeigeWarnungLegende'  : false,
                                                  'zeigeBalken'          : true,
                                                  'absBalken'            : true,
                                                  'zeigeId'              : true,
                                                  'ersetzeAlter'        : true,
                                                  'zeigeAlter'          : true,
                                                  'zeigeQuote'          : true,
                                                  'zeigePosition'        : true,
                                                  'zeigeZatDone'        : true,
                                                  'zeigeZatLeft'        : true,
                                                  'zeigeFixSkills'      : true,
                                                  'zeigeTrainiert'      : true,
                                                  'zeigeAnteilPri'      : true,
                                                  'zeigeAnteilSec'      : true,
                                                  'zeigePrios'          : true,
                                                  'zeigeAufw'            : true,
                                                  'zeigeGeb'            : true,
                                                  'zeigeTal'            : true,
                                                  'anzahlOpti'          : true,
                                                  'anzahlMW'            : true,
                                                  'zeigeTrainiertEnde'  : true,
                                                  'zeigeAnteilPriEnde'  : true,
                                                  'zeigeAnteilSecEnde'  : true,
                                                  'zeigePriosEnde'      : true,
                                                  'zeigeSkillEnde'      : true,
                                                  'anzahlOptiEnde'      : true,
                                                  'anzahlMWEnde'        : true,
                                                  'zatAges'              : true,
                                                  'trainiert'            : true,
                                                  'positions'            : true,
                                                  'skills'              : true,
                                                  'reset'                : true,
                                                  'showForm'            : true
                                              },
                                'formWidth'  : 1
                            }).then(optSet => {
                const __ROWS = getRows(1);
                const __HEADERS = __ROWS[0];
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"
 
                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 3);
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, {
                                                    'Default'            : true,
                                                    'ersetzeSkills'      : false,
                                                    'zeigeSkill'        : false
                                                });
 
                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
 
                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
                    if (__ROWS[i].cells.length > 1) {
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
                    } else {
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }
 
                // Format der Trennlinie zwischen den Jahrgaengen...
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
 
                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
                }
            });
    }
 
    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}
 
(() => {
    (async () => {
        try {
            // URL-Legende:
            // page=0: Managerbuero
            // page=1: Teamuebersicht
            // page=2: Spielereinzelwerte
            // page=3: Opt. Skill
            // page=4: Optionen
 
            // Verzweige in unterschiedliche Verarbeitungen je nach Wert von page:
            switch (getPageIdFromURL(window.location.href, {
                                                              'haupt.php' : 0,  // Ansicht "Haupt" (Managerbuero)
                                                              'ju.php'    : 1  // Ansicht "Jugendteam"
                                                          }, 'page')) {
                case 0  : await procHaupt().catch(defaultCatch); break;
                case 1  : await procTeamuebersicht().catch(defaultCatch); break;
                case 2  : await procSpielereinzelwerte().catch(defaultCatch); break;
                case 3  : await procOptSkill().catch(defaultCatch); break;
                case 4  : await procOptionen().catch(defaultCatch); break;
                default : break;
            }
 
            return 'OK';
        } catch (ex) {
            return defaultCatch(ex);
         }
         }
 
    })().then(rc => {
        // Format der Trennlinie zwischen den Monaten...
             __LOG[1]('SCRIPT END', __DBMOD.Name, '(' + rc + ')');
        const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
         })
 
})();
        separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Age, __ROWOFFSETUPPER, __ROWOFFSETLOWER, -1, 0, floorValue);
    }
}
 
// Verarbeitet Ansicht "Spielereinzelwerte"
function procSpielereinzelwerte() {
    const __ROWOFFSETUPPER = 1;    // Header-Zeile
    const __ROWOFFSETLOWER = 0;
 
    const __COLUMNINDEX = {
             'Flg'  : 0,
            'Land'  : 1,
            'U'    : 2,
            'Age'  : 3,
            'Einz'  : 4,    // ab hier die Einzelskills
            'SCH'  : 4,
            'ABS'  : 4,    // TOR
            'BAK'  : 5,
            'STS'  : 5,    // TOR
            'KOB'  : 6,
            'FAN'  : 6,    // TOR
            'ZWK'  : 7,
            'STB'  : 7,    // TOR
            'DEC'  : 8,
            'SPL'  : 8,    // TOR
            'GES'  : 9,
            'REF'  : 9,    // TOR
            'FUQ'  : 10,
            'ERF'  : 11,
            'AGG'  : 12,
            'PAS'  : 13,
            'AUS'  : 14,
            'UEB'  : 15,
            'WID'  : 16,
            'SEL'  : 17,
            'DIS'  : 18,
            'ZUV'  : 19,
            'EIN'  : 20,
            'Zus'  : 21    // Zusaetze hinter den Einzelskills
        };
 
    if (getRows(1) === undefined) {
        __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else {
        buildOptions(__OPTCONFIG, __OPTSET, {
                        'menuAnchor' : getTable(0, "div"),
                        'hideForm'  : {
                                            'zatAges'      : true,
                                            'trainiert'    : true,
                                            'positions'    : true,
                                            'skills'        : true,
                                            'shortAufw'    : true
                                        },
                        'formWidth' : 1
                    });
 
        const __ROWS = getRows(1);
        const __HEADERS = __ROWS[0];
        const __TITLECOLOR = getColor("LEI");  // "#FFFFFF"
 
        const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, false);
        const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, true);
 
        __COLMAN.addTitles(__HEADERS, __TITLECOLOR);
 
        for (let i = 0; i < __PLAYERS.length; i++) {
            __COLMAN.addValues(__PLAYERS[i], __ROWS[i + __ROWOFFSETUPPER], __TITLECOLOR);
        }
 
        // Format der Trennlinie zwischen den Monaten...
        const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);
 
         separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Age, __ROWOFFSETUPPER, __ROWOFFSETLOWER, -1, 0, floorValue);
    }
}
 
try {
    // URL-Legende:
    // page=0: Managerbuero
    // page=1: Teamuebersicht
    // page=2: Spielereinzelwerte
 
    // Verzweige in unterschiedliche Verarbeitungen je nach Wert von page:
    switch (getPageIdFromURL(window.location.href, {
                                                      'haupt.php' : 0,  // Ansicht "Haupt" (Managerbuero)
                                                      'ju.php'    : 1  // Ansicht "Jugendteam"
                                                  }, 'page')) {
        case 0  : procHaupt(); break;
        case 1  : procTeamuebersicht(); break;
        case 2  : procSpielereinzelwerte(); break;
        default : break;
    }
} catch (ex) {
    showAlert('[' + ex.lineNumber + "] " + __DBMOD.Name, ex.message, ex);
} finally {
    __LOG[2]("SCRIPT END");
}


// *** EOF ***
// *** EOF ***
</pre>
</pre>

Aktuelle Version vom 24. August 2020, 10:08 Uhr

OS2.jugend
Dateiname OS2.jugend.user.js
Version 0.73 (WebExtensions)
Autor Andreas Eckes, Strindheim BK
Sven Loges (SLC), Choromonets Odessa
Beschreibung Jugendteam-Script fuer Online Soccer 2.0
Webseiten
haupt.php Managerbüro
ju.php?page=1 Jugendteam-Teamübersicht
ju.php?page=2 Jugendteam-Spielereinzelwerte
ju.php?page=3 Jugendteam-Opt. Skill
ju.php?page=4 Jugendteam-Optionen
Funktionalität Trennstriche zwischen den Jahrgängen
Aktueller Skill, Opti und MW
Prognose von Opti und MW für Ende Jahrgang 18
Optionen und Menu
Neue Marktwertformel
Automatische Ermittlung des ZATs
Hidden-Optionen und Datenspeicher
Geburtstage und dezimales Alter
Erweiterte Optionen auch auf der Seite
Zusatzspalten Talent/Quote/Aufw./Geb./Alter
Zusatzspalten Quote/Alter/Pos in der Übersicht
Zusatzspalten Alter ersetzen/Aufwertungen kurz+TOR
Zusatzspalten fix/tr./%H/%N/Prios jetzt und Ende
Interaktive Menü-Optionen
Gemeinsame Code- und Datenbasis
Qualitätsbalken
Markierung der Primärskills bei Einzelwerten und Aufwertungen
Beachtung von Jugendförderung und Doppelpositionen
Warnung vor Ende 18 in letzter Periode und mehr am letzten ZAT
Reguläre Ausdrücke im @include
Neues Design und Seite "Opt. Skill"
Gruppierung nach Jahrgängen U13 bis U18 (per Option)
Warnung vor Ende 18 auch im Managerbüro (per Option)
Warnung vor Sperre des Ziehens im Falle eines Aufstiegs
Neuer Jahrgang U19
Letzte Änderung 24.08.2020

Quellcode INSTALLATION[Bearbeiten]

// ==UserScript==
// @name         OS2.jugend
// @namespace    http://os.ongapo.com/
// @version      0.73
// @copyright    2013+
// @author       Sven Loges (SLC) / Andreas Eckes (Strindheim BK)
// @description  Jugendteam-Script fuer Online Soccer 2.0
// @include      /^https?://(www\.)?(os\.ongapo\.com|online-soccer\.eu|os-zeitungen\.com)/haupt\.php(\?changetosecond=\w+(&\S+)*)?$/
// @include      /^https?://(www\.)?(os\.ongapo\.com|online-soccer\.eu|os-zeitungen\.com)/ju\.php(\?page=\d+(&\S+)*)?$/
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @grant        GM.registerMenuCommand
// @grant        GM.info
// @require      https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @grant        GM_info
// ==/UserScript==

// ECMAScript 6:
/* jshint esnext: true */
/* jshint moz: true */

// ==================== Konfigurations-Abschnitt fuer Optionen ====================

const __LOGLEVEL = 3;

// Options-Typen
const __OPTTYPES = {
    'MC' : "multiple choice",
    'SW' : "switch",
    'TF' : "true/false",
    'SD' : "simple data",
    'SI' : "simple option"
};

// Options-Typen
const __OPTACTION = {
    'SET' : "set option value",
    'NXT' : "set next option value",
    'RST' : "reset options"
};

const __OPTMEM = {
    'normal' : {
                   'Name'      : "Browser",
                   'Value'     : localStorage,
                   'Display'   : "localStorage",
                   'Prefix'    : 'run'
               },
    'begrenzt' : {
                   'Name'      : "Session",
                   'Value'     : sessionStorage,
                   'Display'   : "sessionStorage",
                   'Prefix'    : 'run'
               },
    'inaktiv' : {
                   'Name'      : "inaktiv",
                   'Value'     : undefined,
                   'Display'   : "",
                   'Prefix'    : ""
               }
};

// Moegliche Optionen (hier die Standardwerte editieren oder ueber das Benutzermenu setzen):
const __OPTCONFIG = {
    'ersetzeSkills' : {   // Auswahl fuer prognostizierte Einzelskills mit Ende 18 statt der aktuellen (true = Ende 18, false = aktuell)
                   'Name'      : "substSkills",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Einzelwerte Ende",
                   'Hotkey'    : 'E',
                   'AltLabel'  : "Einzelwerte aktuell",
                   'AltHotkey' : 'k',
                   'FormLabel' : "Prognose Einzelwerte"
               },
    'zeigeJahrgang' : {   // Auswahl, ob ueber jedem Jahrgang die Ueberschriften gezeigt werden sollen oder alles in einem Block (true = Jahrgaenge, false = ein Block)
                   'Name'      : "showGroupTitle",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Jahrgangs\xFCberschriften",
                   'Hotkey'    : 'J',
                   'AltLabel'  : "Nur Trennlinie benutzen",
                   'AltHotkey' : 'j',
                   'FormLabel' : "Jahrg\xE4nge gruppieren"
               },
    'zeigeUxx' : {        // Auswahl, ob in der Ueberschrift ueber jedem Jahrgang zusaetzlich zur Saison noch der Jahrgang in der Form 'Uxx' angegeben wird
                   'Name'      : "showUxx",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Jahrg\xE4nge anzeigen",
                   'Hotkey'    : 'U',
                   'AltLabel'  : "Nur Saisons anzeigen",
                   'AltHotkey' : 'u',
                   'FormLabel' : "Jahrg\xE4nge U13 bis U19"
               },
    'zeigeWarnung' : {    // Auswahl, ob eine Warnung erscheint, wenn Talente gezogen werden sollten
                   'Name'      : "showWarning",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Ziehwarnung ein",
                   'Hotkey'    : 'Z',
                   'AltLabel'  : "Ziehwarnung aus",
                   'AltHotkey' : 'Z',
                   'FormLabel' : "Ziehwarnung"
               },
    'zeigeWarnungMonat' : {  // Auswahl, ob eine Warnung erscheint, wenn zum naechsten Abrechnungs-ZAT Talente gezogen werden sollten
                   'Name'      : "showWarningMonth",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Ziehwarnung Monat ein",
                   'Hotkey'    : 'Z',
                   'AltLabel'  : "Ziehwarnung Monat aus",
                   'AltHotkey' : 'Z',
                   'FormLabel' : "Ziehwarnung Monat"
               },
    'zeigeWarnungHome' : {  // Auswahl, ob eine Meldung im Managerbuero erscheint, wenn Talente gezogen werden sollten
                   'Name'      : "showWarningHome",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Ziehwarnung B\xFCro ein",
                   'Hotkey'    : 'z',
                   'AltLabel'  : "Ziehwarnung B\xFCro aus",
                   'AltHotkey' : 'z',
                   'FormLabel' : "Ziehwarnung B\xFCro"
               },
    'zeigeWarnungDialog' : {  // Auswahl, ob die Meldung im Managerbuero als Dialog erscheinen soll
                   'Name'      : "showWarningDialog",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Ziehwarnung B\xFCro als Dialog",
                   'Hotkey'    : 'z',
                   'AltLabel'  : "Ziehwarnung B\xFCro als Textmeldung",
                   'AltHotkey' : 'z',
                   'FormLabel' : "Ziehwarnung B\xFCro Dialog"
               },
    'zeigeWarnungAufstieg' : {  // Auswahl, ob eine Warnung in der Uebersicht erscheint, wenn Talente nach Aufstieg nicht mehr gezogen werden koennen
                   'Name'      : "showWarningAufstieg",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Ziehwarnung Aufstieg ein",
                   'Hotkey'    : 'ä',
                   'AltLabel'  : "Ziehwarnung Aufstieg aus",
                   'AltHotkey' : 'ä',
                   'FormLabel' : "Ziehwarnung Aufstieg"
               },
    'zeigeWarnungLegende' : {  // Auswahl, ob eine extra Meldung in Teamuebersicht erscheint, die dort als Legende dient
                   'Name'      : "showWarningLegende",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Ziehwarnung Legende ein",
                   'Hotkey'    : 'L',
                   'AltLabel'  : "Ziehwarnung Legende aus",
                   'AltHotkey' : 'L',
                   'FormLabel' : "Ziehwarnung Legende"
               },
    'zeigeBalken' : {     // Spaltenauswahl fuer den Qualitaetsbalken des Talents (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showRatioBar",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Balken Qualit\xE4t ein",
                   'Hotkey'    : 'B',
                   'AltLabel'  : "Balken Qualit\xE4t aus",
                   'AltHotkey' : 'B',
                   'FormLabel' : "Balken Qualit\xE4t"
               },
    'absBalken' : {       // Spaltenauswahl fuer den Guetebalken des Talents absolut statt nach Foerderung (true = absolut, false = relativ nach Foerderung)
                   'Name'      : "absBar",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Balken absolut",
                   'Hotkey'    : 'u',
                   'AltLabel'  : "Balken nach F\xF6rderung",
                   'AltHotkey' : 'u',
                   'FormLabel' : "Balken 100%"
               },
    'zeigeId' : {         // Spaltenauswahl fuer Identifizierungsmerkmale der Jugendspieler (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showFingerprints",
                   'Type'      : __OPTTYPES.SW,
                   'Hidden'    : true,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Identifikation ein",
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Identifikation aus",
                   'AltHotkey' : 'T',
                   'FormLabel' : "Identifikation"
               },
    'zeigeTal' : {        // Spaltenauswahl fuer Talente (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showTclasses",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Talent ein",
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Talent aus",
                   'AltHotkey' : 'T',
                   'FormLabel' : "Talent"
               },
    'zeigeQuote' : {      // Spaltenauswahl fuer Aufwertungsschnitt (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showRatio",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Quote ein",
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Quote aus",
                   'AltHotkey' : 'T',
                   'FormLabel' : "Quote"
               },
    'zeigeGeb' : {        // Spaltenauswahl fuer Geburtstage (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showBirthday",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Geburtstag ein",
                   'Hotkey'    : 'G',
                   'AltLabel'  : "Geburtstag aus",
                   'AltHotkey' : 'G',
                   'FormLabel' : "Geburtstag"
               },
    'zeigeAlter' : {      // Spaltenauswahl fuer dezimales Alter (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showAge",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Alter ein",
                   'Hotkey'    : 'A',
                   'AltLabel'  : "Alter aus",
                   'AltHotkey' : 'A',
                   'FormLabel' : "Alter"
               },
    'ersetzeAlter' : {    // Spaltenauswahl fuer dezimales Alter statt ganzzahligen Alters (true = Dezimalbruch, false = ganzzahlig)
                   'Name'      : "substAge",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Alter dezimal",
                   'Hotkey'    : 'd',
                   'AltLabel'  : "Alter ganzzahlig",
                   'AltHotkey' : 'g',
                   'FormLabel' : "Alter ersetzen"
               },
    'zeigeAufw' : {       // Spaltenauswahl fuer Aufwertungen (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showProgresses",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Aufwertungen ein",
                   'Hotkey'    : 'W',
                   'AltLabel'  : "Aufwertungen aus",
                   'AltHotkey' : 'W',
                   'FormLabel' : "Aufwertungen"
               },
    'shortAufw' : {       // Abgekuerzte Aufwertungsanzeige
                   'Name'      : "shortProgresses",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Aufwertungen kurz",
                   'Hotkey'    : 'A',
                   'AltLabel'  : "Aufwertungen lang",
                   'AltHotkey' : 'A',
                   'FormLabel' : "Kurze Aufwertungen"
               },
    'zeigeZatDone' : {    // Spaltenauswahl fuer die Anzahl der bisherigen Trainings-ZATs (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showFixZatDone",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Trainings-ZATs ein",
                   'Hotkey'    : 'Z',
                   'AltLabel'  : "Trainings-ZATs aus",
                   'AltHotkey' : 'Z',
                   'FormLabel' : "Trainings-ZATs"
               },
    'zeigeZatLeft' : {    // Spaltenauswahl fuer die Anzahl der Rest-ZATs bis Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showFixZatLeft",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Rest-ZATs ein",
                   'Hotkey'    : 'R',
                   'AltLabel'  : "Rest-ZATs aus",
                   'AltHotkey' : 'R',
                   'FormLabel' : "Rest-ZATs"
               },
    'zeigeFixSkills' : {  // Spaltenauswahl fuer die Summe der Fixskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showFixSkills",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Fixskills ein",
                   'Hotkey'    : 'F',
                   'AltLabel'  : "Fixskills aus",
                   'AltHotkey' : 'F',
                   'FormLabel' : "Fixskills"
               },
    'zeigeTrainiert' : {  // Spaltenauswahl fuer die aktuellen trainierten Skills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showTrainiert",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Trainiert ein",
                   'Hotkey'    : 'T',
                   'AltLabel'  : "Trainiert aus",
                   'AltHotkey' : 'T',
                   'FormLabel' : "Trainiert"
               },
    'zeigeAnteilPri' : {  // Spaltenauswahl fuer den prozentualen Anteil der aktuellen Hauptskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showAnteilPri",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Anteil Hauptskills ein",
                   'Hotkey'    : 'H',
                   'AltLabel'  : "Anteil Hauptskills aus",
                   'AltHotkey' : 'H',
                   'FormLabel' : "Anteil Hauptskills"
               },
    'zeigeAnteilSec' : {  // Spaltenauswahl fuer den prozentualen Anteil der aktuellen Nebenskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showAnteilSec",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Anteil Nebenskills ein",
                   'Hotkey'    : 'N',
                   'AltLabel'  : "Anteil Nebenskills aus",
                   'AltHotkey' : 'N',
                   'FormLabel' : "Anteil Nebenskills"
               },
    'zeigePrios' : {      // Spaltenauswahl fuer den Schnitt der Hauptskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showPrios",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Prios ein",
                   'Hotkey'    : 'r',
                   'AltLabel'  : "Prios aus",
                   'AltHotkey' : 'r',
                   'FormLabel' : "Prios"
               },
    'zeigeSkill' : {      // Spaltenauswahl fuer die aktuellen Werte (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showSkill",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Skill ein",
                   'Hotkey'    : 'S',
                   'AltLabel'  : "Skill aus",
                   'AltHotkey' : 'S',
                   'FormLabel' : "Skill"
               },
    'zeigePosition' : {   // Position anzeigen
                   'Name'      : "showPos",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Position ein",
                   'Hotkey'    : 'P',
                   'AltLabel'  : "Position aus",
                   'AltHotkey' : 'P',
                   'FormLabel' : "Position"
               },
    'anzahlOpti' : {      // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "anzOpti",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'Number',
                   'SelValue'  : false,
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'   : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Opti: beste $",
                   'Hotkey'    : 'O',
                   'FormLabel' : "Opti:|beste $"
               },
    'anzahlMW' : {        // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "anzMW",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'Number',
                   'SelValue'  : false,
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'   : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "MW: beste $",
                   'Hotkey'    : 'M',
                   'FormLabel' : "MW:|beste $"
               },
    'zeigeTrainiertEnde' : {  // Spaltenauswahl fuer die trainierten Skills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showTrainiertEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Trainiert Ende ein",
                   'Hotkey'    : 'n',
                   'AltLabel'  : "Trainiert Ende aus",
                   'AltHotkey' : 'n',
                   'FormLabel' : "Trainiert \u03A9"
               },
    'zeigeAnteilPriEnde' : {  // Spaltenauswahl fuer den prozentualen Anteil der Hauptskills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showAnteilPriEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Anteil Hauptskills Ende ein",
                   'Hotkey'    : 'u',
                   'AltLabel'  : "Anteil Hauptskills Ende aus",
                   'AltHotkey' : 'u',
                   'FormLabel' : "Anteil Hauptskills \u03A9"
               },
    'zeigeAnteilSecEnde' : {  // Spaltenauswahl fuer den prozentualen Anteil der Nebenskills mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showAnteilSecEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : false,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Anteil Nebenskills Ende ein",
                   'Hotkey'    : 'b',
                   'AltLabel'  : "Anteil Nebenskills Ende aus",
                   'AltHotkey' : 'b',
                   'FormLabel' : "Anteil Nebenskills \u03A9"
               },
    'zeigePriosEnde' : {  // Spaltenauswahl fuer den Schnitt der Hauptskills (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showPriosEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Prios Ende ein",
                   'Hotkey'    : 'o',
                   'AltLabel'  : "Prios Ende aus",
                   'AltHotkey' : 'o',
                   'FormLabel' : "Prios \u03A9"
               },
    'zeigeSkillEnde' : {  // Spaltenauswahl fuer die Werte mit Ende 18 (true = anzeigen, false = nicht anzeigen)
                   'Name'      : "showSkillEnde",
                   'Type'      : __OPTTYPES.SW,
                   'Default'   : true,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Skill Ende ein",
                   'Hotkey'    : 'i',
                   'AltLabel'  : "Skill Ende aus",
                   'AltHotkey' : 'i',
                   'FormLabel' : "Skill \u03A9"
               },
    'anzahlOptiEnde' : {  // Spaltenauswahl fuer die Werte mit Ende 18:
                          // Gibt die Anzahl der Opti-Spalten an / 1: nur bester Opti, 2: die beiden besten, ..., 6: Alle inklusive TOR
                          // Bei Torhuetern wird immer nur der TOR-Opti angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "anzOptiEnde",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'Number',
                   'SelValue'  : false,
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'   : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Opti Ende: beste $",
                   'Hotkey'    : 't',
                   'FormLabel' : "Opti \u03A9:|beste $"
               },
    'anzahlMWEnde' : {    // Spaltenauswahl fuer die Werte mit Ende 18:
                          // Gibt die Anzahl der MW-Spalten an / 1: nur hoechsten MW, 2: die beiden hoechsten, ..., 6: Alle inklusive TOR
                          // Bei Torhuetern wird immer nur der TOR-MW angezeigt / Werte < 1 oder > 6 schalten die Anzeige aus
                   'Name'      : "anzMWEnde",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'Number',
                   'SelValue'  : false,
                   'Choice'    : [ 0, 1, 2, 3, 4, 5, 6 ],
                   'Default'   : 1,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "MW Ende: beste $",
                   'Hotkey'    : 'W',
                   'FormLabel' : "MW \u03A9:|beste $"
               },
    'kennzeichenEnde' : {  // Markierung fuer Ende 18
                   'Name'      : "charEnde",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'String',
                   'FreeValue' : true,
                   'MinChoice' : 0,
                   'Choice'    : [ " \u03A9", " 18" ],
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Ende: $",
                   'Hotkey'    : 'E',
                   'FormLabel' : "Ende 18:|$"
               },
    'sepStyle' : {        // Stil der Trennlinie
                   'Name'      : "sepStyle",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'String',
                   'Choice'    : [ 'solid', 'hidden', 'dotted', 'dashed', 'double', 'groove', 'ridge',
                                   'inset', 'outset', 'none' ],
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Stil: $",
                   'Hotkey'    : 'l',
                   'FormLabel' : "Stil:|$"
               },
    'sepColor' : {        // Farbe der Trennlinie
                   'Name'      : "sepColor",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'String',
                   'FreeValue' : true,
                   'Choice'    : [ 'white', 'yellow', 'black', 'blue', 'cyan', 'gold', 'grey', 'green',
                                   'lime', 'magenta', 'maroon', 'navy', 'olive', 'orange', 'purple',
                                   'red', 'teal', 'transparent' ],
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Farbe: $",
                   'Hotkey'    : 'F',
                   'FormLabel' : "Farbe:|$"
               },
    'sepWidth' : {        // Dicke der Trennlinie
                   'Name'      : "sepWidth",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'String',
                   'FreeValue' : true,
                   'Choice'    : [ 'thin', 'medium', 'thick' ],
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Dicke: $",
                   'Hotkey'    : 'D',
                   'FormLabel' : "Dicke:|$"
               },
    'saison' : {          // Laufende Saison
                   'Name'      : "saison",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'Number',
                   'FreeValue' : true,
                   'SelValue'  : false,
                   'Choice'    : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],
                   'Default'   : 16,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Saison: $",
                   'Hotkey'    : 'a',
                   'FormLabel' : "Saison:|$"
               },
    'aktuellerZat' : {    // Laufender ZAT
                   'Name'      : "currZAT",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'Number',
                   'Permanent' : true,
                   'SelValue'  : false,
                   'Choice'    : [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
                                  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
                                  24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
                                  36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
                                  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
                                  60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
                                  72 ],
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "ZAT: $",
                   'Hotkey'    : 'Z',
                   'FormLabel' : "ZAT:|$"
               },
    'datenZat' : {        // Stand der Daten zum Team und ZAT
                   'Name'      : "dataZAT",
                   'Type'      : __OPTTYPES.SD,
                   'ValType'   : 'Number',
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : undefined,
                   'Action'    : __OPTACTION.SET,
                   'Submit'    : undefined,
                   'Cols'      : 1,
                   'Rows'      : 1,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Daten-ZAT:"
               },
    'oldDatenZat' : {     // Stand der Daten zum Team und ZAT
                   'Name'      : "oldDataZAT",
                   'Type'      : __OPTTYPES.SD,
                   'ValType'   : 'Number',
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : undefined,
                   'Action'    : __OPTACTION.SET,
                   'Submit'    : undefined,
                   'Cols'      : 1,
                   'Rows'      : 1,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Vorheriger Daten-ZAT:"
               },
    'foerderung' : {      // Jugendfoerderung
                   'Name'      : "donation",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'Number',
                   'Permanent' : true,
                   'SelValue'  : false,
                   'Choice'    : [ 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000,
                                  5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000 ],
                   'Default'   : 10000,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "F\xF6rderung: $",
                   'Hotkey'    : 'F',
                   'FormLabel' : "F\xF6rderung:|$"
               },
    'team' : {            // Datenspeicher fuer Daten des Erst- bzw. Zweitteams
                   'Name'      : "team",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : false,
                   'Serial'    : true,
                   'Permanent' : true,
                   'Default'   : undefined,  // new Team() // { 'Team' : undefined, 'Liga' : undefined, 'Land' : undefined, 'LdNr' : 0, 'LgNr' : 0 }
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 6,
                   'Replace'   : null,
                   'Space'     : 1,
                   'Label'     : "Verein:"
               },
    'fingerprints' : {    // Datenspeicher fuer Identifizierungsmerkmale der Jugendspieler
                   'Name'      : "fingerprints",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 6,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Identifikation:"
               },
    'birthdays' : {       // Datenspeicher fuer Geburtstage der Jugendspieler
                   'Name'      : "birthdays",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 2,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Geburtstage:"
               },
    'tClasses' : {        // Datenspeicher fuer Talente der Jugendspieler (-1=wenig, 0=normal, +1=hoch)
                   'Name'      : "tClasses",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 2,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Talente:"
               },
    'progresses' : {      // Datenspeicher fuer Aufwertungen der Jugendspieler (als Strings)
                   'Name'      : "progresses",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 7,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Aufwertungen:"
               },
    'ziehAnz' : {         // Datenspeicher fuer Anzahl zu ziehender Jugendspieler bis zur naechsten Abrechnung
                   'Name'      : "drawCounts",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : false,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 25,
                   'Rows'      : 1,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Zu ziehen:"
               },
    'ziehAnzAufstieg' : { // Datenspeicher fuer Anzahl zu ziehender Jugendspieler bis zur naechsten Abrechnung im Falle eines Aufstiegs
                   'Name'      : "drawCountsAufstieg",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'Number',
                   'Hidden'    : true,
                   'AutoReset' : false,
                   'Permanent' : true,
                   'FreeValue' : true,
                   'SelValue'  : false,
                   'Choice'    : [ 0, 1, 2, 3, 4, 5 ],
                   'Default'   : 0,
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Zu ziehen bei Aufstieg: $",
                   'Hotkey'    : 'z',
                   'FormLabel' : "Zu ziehen bei Aufstieg:|$"
               },
    'zatAges' : {         // Datenspeicher fuer (gebrochene) Alter der Jugendspieler
                   'Name'      : "zatAges",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 2,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "ZAT-Alter:"
               },
    'trainiert' : {       // Datenspeicher fuer Trainingsquoten der Jugendspieler
                   'Name'      : "numProgresses",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 2,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Trainiert:"
               },
    'positions' : {       // Datenspeicher fuer optimale Positionen der Jugendspieler
                   'Name'      : "positions",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 3,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Positionen:"
               },
    'skills' : {          // Datenspeicher fuer aktuelle Einzelskills der Jugendspieler
                   'Name'      : "skills",
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'AutoReset' : true,
                   'Permanent' : true,
                   'Default'   : [],
                   'Submit'    : undefined,
                   'Cols'      : 36,
                   'Rows'      : 20,
                   'Replace'   : null,
                   'Space'     : 0,
                   'Label'     : "Skills:"
               },
    'hauptLS'  : {        // Option 'ligaSize' aus Modul 'OS2.haupt', hier als 'hauptLS'
                   'Shared'    : { /*'namespace' : "http://os.ongapo.com/",*/ 'module' : "OS2.haupt", 'item' : 'ligaSize' },
                   'Hidden'    : true,
                   'FormLabel' : "Liga:|$er (haupt)"
               },
    'hauptZat' : {        // Option 'datenZat' aus Modul 'OS2.haupt', hier als 'hauptZat'
                   'Shared'    : { /*'namespace' : "http://os.ongapo.com/",*/ 'module' : "OS2.haupt", 'item' : 'datenZat' },
                   'Hidden'    : true,
                   'Cols'      : 36,
                   'Rows'      : 6,
                   'Label'     : "ZAT:"
               },
    'haupt' : {           // Alle Optionen des Moduls 'OS2.haupt'
                   'Shared'    : { 'module' : "OS2.haupt", 'item' : '$' },
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'Cols'      : 36,
                   'Rows'      : 6,
                   'Replace'   : null,
                   'Space'     : 4,
                   'Label'     : "Haupt:"
               },
    'data' : {            // Optionen aller Module
                   'Shared'    : { 'module' : '$' },
                   'Type'      : __OPTTYPES.SD,
                   'Hidden'    : true,
                   'Serial'    : true,
                   'Cols'      : 36,
                   'Rows'      : 6,
                   'Replace'   : null,
                   'Space'     : 4,
                   'Label'     : "Data:"
               },
    'reset' : {           // Optionen auf die "Werkseinstellungen" zuruecksetzen
                   'FormPrio'  : undefined,
                   'Name'      : "reset",
                   'Type'      : __OPTTYPES.SI,
                   'Action'    : __OPTACTION.RST,
                   'Label'     : "Standard-Optionen",
                   'Hotkey'    : 'r',
                   'FormLabel' : ""
               },
    'storage' : {         // Browserspeicher fuer die Klicks auf Optionen
                   'FormPrio'  : undefined,
                   'Name'      : "storage",
                   'Type'      : __OPTTYPES.MC,
                   'ValType'   : 'String',
                   'Choice'    : Object.keys(__OPTMEM),
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Speicher: $",
                   'Hotkey'    : 'c',
                   'FormLabel' : "Speicher:|$"
               },
    'oldStorage' : {      // Vorheriger Browserspeicher fuer die Klicks auf Optionen
                   'FormPrio'  : undefined,
                   'Name'      : "oldStorage",
                   'Type'      : __OPTTYPES.SD,
                   'PreInit'   : true,
                   'AutoReset' : true,
                   'Hidden'    : true
               },
    'showForm' : {        // Optionen auf der Webseite (true = anzeigen, false = nicht anzeigen)
                   'FormPrio'  : 1,
                   'Name'      : "showForm",
                   'Type'      : __OPTTYPES.SW,
                   'FormType'  : __OPTTYPES.SI,
                   'Permanent' : true,
                   'Default'   : false,
                   'Title'     : "$V Optionen",
                   'Action'    : __OPTACTION.NXT,
                   'Label'     : "Optionen anzeigen",
                   'Hotkey'    : 'O',
                   'AltTitle'  : "$V schlie\xDFen",
                   'AltLabel'  : "Optionen verbergen",
                   'AltHotkey' : 'O',
                   'FormLabel' : ""
               }
};

// ==================== Invarianter Abschnitt fuer Optionen ====================

// Ein Satz von Logfunktionen, die je nach Loglevel zur Verfuegung stehen. Aufruf: __LOG[level](text)
const __LOG = {
                  'logFun'    : [
                                    console.error,  // [0] Alert
                                    console.error,  // [1] Error
                                    console.log,    // [2] Log: Release
                                    console.log,    // [3] Log: Info
                                    console.log,    // [4] Log: Debug
                                    console.log,    // [5] Log: Verbose
                                    console.log,    // [6] Log: Very verbose
                                    console.warn    // [7] Log: Testing
                                ],
                  'init'      : function(win, logLevel = 1) {
                                    for (let level = 0; level < this.logFun.length; level++) {
                                        this[level] = ((level > logLevel) ? function() { } : this.logFun[level]);
                                    }
                                },
                  'stringify' : safeStringify,      // JSON.stringify
                  'changed'   : function(oldVal, newVal) {
                                    const __OLDVAL = this.stringify(oldVal);
                                    const __NEWVAL = this.stringify(newVal);

                                    return ((__OLDVAL !== __NEWVAL) ? __OLDVAL + " => " : "") + __NEWVAL;
                                }
              };

__LOG.init(window, __LOGLEVEL);

// Kompatibilitaetsfunktion zur Ermittlung des Namens einer Funktion (falle <Function>.name nicht vorhanden ist)
if (Function.prototype.name === undefined) {
    Object.defineProperty(Function.prototype, 'name', {
            get : function() {
                      return /function ([^(\s]*)/.exec(this.toString())[1];
                  }
        });
}

// Ergaenzung fuer Strings: Links oder rechts auffuellen nach Vorlage
// padStr: Vorlage, z.B. "00" fuer zweistellige Zahlen
// padLeft: true = rechtsbuendig, false = linksbuendig
// clip: Abschneiden, falls zu lang
// return Rechts- oder linksbuendiger String, der so lang ist wie die Vorlage
String.prototype.pad = function(padStr, padLeft = true, clip = false) {
    const __LEN = ((clip || (padStr.length > this.length)) ? padStr.length : this.length);

    return (padLeft ? String(padStr + this).slice(- __LEN) : String(this + padStr).slice(0, __LEN));
};

// Ersetzt in einem String {0}, {1}, ... durch die entsprechenden Parameter
// arguments: Parameter, die fuer {0}, {1}, ... eingesetzt werden sollen
// return Resultierender String
String.prototype.format = function() {
    const __ARGS = arguments;
    return this.replace(/{(\d+)}/g, function(match, argIdx) {
                                        const __ARG = __ARGS[argIdx];
                                        return ((__ARG !== undefined) ? __ARG : match);
                                    });
};

// Gibt eine Meldung in der Console aus und oeffnet ein Bestaetigungsfenster mit der Meldung
// label: Eine Ueberschrift
// message: Der Meldungs-Text
// data: Ein Wert. Ist er angegeben, wird er in der Console ausgegeben
// return Liefert die Parameter zurueck
function showAlert(label, message, data = undefined) {
    __LOG[0](label + ": " + message);

    if (data !== undefined) {
        __LOG[2](data);
    }

    alert(label + "\n\n" + message);

    return arguments;
}

// Gibt eine Meldung in der Console aus und oeffnet ein Bestaetigungsfenster
// mit der Meldung zu einer Exception oder einer Fehlermeldung
// label: Eine Ueberschrift
// ex: Exception oder sonstiges Fehlerobjekt
// return Liefert die showAlert()-Parameter zurueck
function showException(label, ex) {
    if (ex && ex.message) {  // Exception
        showAlert(label, ex.message, ex);
    } else {  // sonstiger Fehler
        showAlert(label, ex);
    }
}

// Standard-Callback-Funktion fuer onRejected, also abgefangener Fehler
// in einer Promise bei Exceptions oder Fehler bzw. Rejections
// error: Parameter von reject() im Promise-Objekt, der von Promise.catch() erhalten wurde
// return Liefert die showAlert()-Parameter zurueck
function defaultCatch(error) {
    try {
        const __LABEL = `[${error.lineNumber}] ${__DBMOD.Name}`;

        if (error && error.message) {  // Exception
            return showException(__LABEL, error.message, error);
        } else {
            return showException(__LABEL, error);
        }
    } catch (ex) {
        return showException(`[${ex.lineNumber}] ${__DBMOD.Name}`, ex.message, ex);
    }
}

// ==================== Abschnitt fuer Klasse Class ====================

function Class(className, baseClass, initFun) {
    'use strict';

    try {
        const __BASE = ((baseClass !== undefined) ? baseClass : Object);
        const __BASEPROTO = (__BASE ? __BASE.prototype : undefined);
        const __BASECLASS = (__BASEPROTO ? __BASEPROTO.__class : undefined);

        this.className = (className || '?');
        this.baseClass = __BASECLASS;
        Object.setConst(this, 'baseProto', __BASEPROTO, false);

        if (! initFun) {
            const __BASEINIT = (__BASECLASS || { }).init;

            if (__BASEINIT) {
                initFun = function() {
                              // Basisklassen-Init aufrufen...
                              return __BASEINIT.call(this, arguments);
                          };
            } else {
                initFun = function() {
                              // Basisklassen-Init fehlt (und Basisklasse ist nicht Object)...
                              return false;
                          };
            }
        }

        console.assert((__BASE === null) || ((typeof __BASE) === 'function'), "No function:", __BASE);
        console.assert((typeof initFun) === 'function', "Not a function:", initFun);

        this.init = initFun;
    } catch (ex) {
        showAlert('[' + ex.lineNumber + "] Error in Class " + className, ex.message, ex);
    }
}

Class.define = function(subClass, baseClass, members = undefined, initFun = undefined, createProto = true) {
        return (subClass.prototype = subClass.subclass(baseClass, members, initFun, createProto));
    };

Object.setConst = function(obj, item, value, config) {
        return Object.defineProperty(obj, item, {
                        enumerable   : false,
                        configurable : (config || true),
                        writable     : false,
                        value        : value
                    });
    };

Object.setConst(Object.prototype, 'subclass', function(baseClass, members, initFun, createProto) {
        'use strict';

        try {
            const __MEMBERS = (members || { });
            const __CREATEPROTO = ((createProto === undefined) ? true : createProto);

            console.assert((typeof this) === 'function', "Not a function:", this);
            console.assert((typeof __MEMBERS) === 'object', "Not an object:", __MEMBERS);

            const __CLASS = new Class(this.name || __MEMBERS.__name, baseClass, initFun || __MEMBERS.__init);
            const __PROTO = (__CREATEPROTO ? Object.create(__CLASS.baseProto) : this.prototype);

            for (let item in __MEMBERS) {
                if ((item !== '__name') && (item !== '__init')) {
                    Object.setConst(__PROTO, item, __MEMBERS[item]);
                }
            }

            Object.setConst(__PROTO, '__class', __CLASS, ! __CREATEPROTO);

            return __PROTO;
        } catch (ex) {
            showAlert('[' + ex.lineNumber + "] Error in subclassing", ex.message, ex);
        }
    }, false);

Class.define(Object, null, {
                    '__init'       : function() {
                                         // Oberstes Basisklassen-Init...
                                         return true;
                                     },
                    'getClass'     : function() {
                                         return this.__class;
                                     },
                    'getClassName' : function() {
                                         const __CLASS = this.getClass();

                                         return (__CLASS ? __CLASS.getName() : undefined);
                                     },
                    'setConst'     : function(item, value, config = undefined) {
                                         return Object.setConst(this, item, value, config);
                                     }
                }, undefined, false);

Class.define(Function, Object);

Class.define(Class, Object, {
                    'getName'      : function() {
                                         return this.className;
                                     }
                });

// ==================== Ende Abschnitt fuer Klasse Class ====================

// ==================== Abschnitt fuer Klasse Delims ====================

// Basisklasse fuer die Verwaltung der Trennzeichen und Symbole von Pfaden
// delim: Trennzeichen zwischen zwei Ebenen (oder Objekt/Delims mit entsprechenden Properties)
// back: (Optional) Name des relativen Vaterverzeichnisses
// root: (Optional) Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// home: (Optional) Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Delims(delim, back, root, home) {
    'use strict';

    if ((typeof delim) === 'object') {
        // Erster Parameter ist Objekt mit den Properties...
        if (back === undefined) {
            back = delim.back;
        }
        if (root === undefined) {
            root = delim.root;
        }
        if (home === undefined) {
            home = delim.home;
        }
        delim = delim.delim;
    }

    this.setDelim(delim);
    this.setBack(back);
    this.setRoot(root);
    this.setHome(home);
}

Class.define(Delims, Object, {
              'setDelim'       : function(delim = undefined) {
                                     this.delim = delim;
                                 },
              'setBack'        : function(back = undefined) {
                                     this.back = back;
                                 },
              'setRoot'        : function(root = undefined) {
                                     this.root = root;
                                 },
              'setHome'        : function(home = undefined) {
                                     this.home = home;
                                 }
          });

// ==================== Ende Abschnitt fuer Klasse Delims ====================

// ==================== Abschnitt fuer Klasse UriDelims ====================

// Basisklasse fuer die Verwaltung der Trennzeichen und Symbole von URIs
// delim: Trennzeichen zwischen zwei Ebenen (oder Objekt/Delims mit entsprechenden Properties)
// back: (Optional) Name des relativen Vaterverzeichnisses
// root: (Optional) Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// home: (Optional) Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
// scheme: (Optional) Trennzeichen fuer den Schema-/Protokollnamen vorne
// host: (Optional) Prefix fuer Hostnamen hinter dem Scheme-Trenner
// port: (Optional) Trennzeichen vor der Portangabe, falls vorhanden
// query: (Optional) Trennzeichen fuer die Query-Parameter hinter dem Pfad
// parSep: (Optional) Trennzeichen zwischen je zwei Parametern
// parAss: (Optional) Trennzwischen zwischen Key und Value
// node: (Optional) Trennzeichen fuer den Knotennamen hinten (Fragment, Kapitel)
function UriDelims(delim, back, root, home, scheme, host, port, query, qrySep, qryAss, node) {
    'use strict';

    if ((typeof delim) === 'object') {
        // Erster Parameter ist Objekt mit den Properties...
        if (scheme === undefined) {
            scheme = delim.scheme;
        }
        if (host === undefined) {
            host = delim.host;
        }
        if (port === undefined) {
            port = delim.port;
        }
        if (query === undefined) {
            query = delim.query;
        }
        if (qrySep === undefined) {
            qrySep = delim.qrySep;
        }
        if (qryAss === undefined) {
            qryAss = delim.qryAss;
        }
        if (node === undefined) {
            node = delim.node;
        }
    }

    Delims.call(this, delim, back, root, home);

    this.setScheme(scheme);
    this.setHost(host);
    this.setPort(port);
    this.setQuery(query);
    this.setQrySep(qrySep);
    this.setQryAss(qryAss);
    this.setNode(node);
}

Class.define(UriDelims, Delims, {
              'setScheme'      : function(scheme = undefined) {
                                     this.scheme = scheme;
                                 },
              'setHost'        : function(host = undefined) {
                                     this.host = host;
                                 },
              'setPort'        : function(port = undefined) {
                                     this.port = port;
                                 },
              'setQuery'       : function(query = undefined) {
                                     this.query = query;
                                 },
              'setQrySep'      : function(qrySep = undefined) {
                                     this.qrySep = qrySep;
                                 },
              'setQryAss'      : function(qryAss = undefined) {
                                     this.qryAss = qryAss;
                                 },
              'setNode'        : function(node = undefined) {
                                     this.node = node;
                                 }
          });

// ==================== Ende Abschnitt fuer Klasse UriDelims ====================

// ==================== Abschnitt fuer Klasse Path ====================

// Basisklasse fuer die Verwaltung eines Pfades
// homePath: Absoluter Startpfad als String
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Path(homePath, delims) {
    'use strict';

    this.dirs = [];
    this.setDelims(delims);
    this.homeDirs = this.getDirs(homePath, { 'home' : "" });

    this.home();
}

Class.define(Path, Object, {
                  'root'           : function() {
                                         this.dirs.splice(0, this.dirs.length);
                                     },
                  'home'           : function() {
                                         this.dirs = this.homeDirs.slice();
                                     },
                  'up'             : function() {
                                         this.dirs.pop();
                                     },
                  'down'           : function(subDir) {
                                         this.dirs.push(subDir);
                                     },
                  'setDelims'      : function(delims = undefined) {
                                         this.setConst('delims', new Delims(delims));
                                     },
                  'setDelim'       : function(delim = undefined) {
                                         this.delims.setDelim(delim || '/');
                                     },
                  'setBackDelim'   : function(backDelim = undefined) {
                                         this.delims.setBack(backDelim || "..");
                                     },
                  'setRootDelim'   : function(rootDelim = undefined) {
                                         this.delims.setRoot(rootDelim || "");
                                     },
                  'setHomeDelim'   : function(homeDelim = undefined) {
                                         this.delims.setHome(homeDelim || '~');
                                     },
                  'setSchemeDelim' : function(schemeDelim = undefined) {
                                         this.delims.setScheme(schemeDelim || ':');
                                     },
                  'setHostDelim'   : function(hostDelim = undefined) {
                                         this.delims.setHost(hostDelim || '//');
                                     },
                  'setPortDelim'   : function(portDelim = undefined) {
                                         this.delims.setHost(portDelim || ':');
                                     },
                  'setQueryDelim'  : function(queryDelim = undefined) {
                                         this.delims.setQuery(queryDelim || '?');
                                     },
                  'setParSepDelim' : function(parSepDelim = undefined) {
                                         this.delims.setParSep(parSepDelim || '&');
                                     },
                  'setParAssDelim' : function(parAssDelim = undefined) {
                                         this.delims.setParAss(parAssDelim || '=');
                                     },
                  'setNodeDelim'   : function(nodeDelim = undefined) {
                                         this.delims.setNode(nodeDelim || '#');
                                     },
                  'getLeaf'        : function(dirs = undefined) {
                                         const __DIRS = (dirs || this.dirs);

                                         return ((__DIRS && __DIRS.length) ? __DIRS.slice(-1)[0] : "");
                                     },
                  'getPath'        : function(dirs = undefined, delims = undefined) {
                                         const __DELIMS = new Delims(delims);
                                         const __DELIM = (__DELIMS.delim || this.delims.delim);
                                         const __ROOTDELIM = ((__DELIMS.root !== undefined) ? __DELIMS.root : this.delims.root);
                                         const __DIRS = (dirs || this.dirs);

                                         return __ROOTDELIM + __DELIM + __DIRS.join(__DELIM);
                                     },
                  'getDirs'        : function(path = undefined, delims = undefined) {
                                         const __DELIMS = new Delims(delims);
                                         const __DELIM = (__DELIMS.delim || this.delims.delim);
                                         const __ROOTDELIM = ((__DELIMS.root !== undefined) ? __DELIMS.root : this.delims.root);
                                         const __HOMEDELIM = ((__DELIMS.home !== undefined) ? __DELIMS.home : this.delims.home);
                                         const __DIRS = (path ? path.split(__DELIM) : []);
                                         const __FIRST = __DIRS[0];

                                         if (__FIRST && (__FIRST !== __ROOTDELIM) && (__FIRST !== __HOMEDELIM)) {
                                             showAlert("Kein absoluter Pfad", this.getPath(__DIRS), this);
                                         }

                                         return __DIRS.slice(1);
                                     }
                });

// ==================== Ende Abschnitt fuer Klasse Path ====================

// ==================== Abschnitt fuer Klasse URI ====================

// Basisklasse fuer die Verwaltung einer URI/URL
// homePath: Absoluter Startpfad als String
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function URI(homePath, delims) {
    'use strict';

    Path.call(this);

    const __HOSTPORT = this.getHostPort(homePath);

    this.scheme = this.getSchemePrefix(homePath);
    this.host = __HOSTPORT.host;
    this.port = this.parseValue(__HOSTPORT.port);
    this.query = this.parseQuery(this.getQueryString(homePath));
    this.node = this.getNodeSuffix(homePath);

    this.homeDirs = this.getDirs(homePath, { 'home' : "" });

    this.home();
}

Class.define(URI, Path, {
               'setDelims'         : function() {
                                         this.setConst('delims', new UriDelims('/', "..", "", '~', ':', "//", ':', '?', '&', '=', '#'));
                                     },
               'setSchemeDelim'    : function(schemeDelim = undefined) {
                                         this.delims.setScheme(schemeDelim || ':');
                                     },
               'setQueryDelim'     : function(queryDelim = undefined) {
                                         this.delims.setQuery(queryDelim || '?');
                                     },
               'setParSepDelim'    : function(parSepDelim = undefined) {
                                         this.delims.setParSep(parSepDelim || '&');
                                     },
               'setParAssDelim'    : function(parAssDelim = undefined) {
                                         this.delims.setParAss(parAssDelim || '=');
                                     },
               'setNodeDelim'      : function(nodeDelim = undefined) {
                                         this.delims.setNode(nodeDelim || '#');
                                     },
               'getServerPath'     : function(path = undefined) {
                                         return this.stripHostPort(this.stripQueryString(this.stripNodeSuffix(this.stripSchemePrefix(path))));
                                     },
               'getHostPort'       : function(path = undefined) {
                                         const __HOSTDELIM = this.delims.host;
                                         const __PORTDELIM = this.delims.port;
                                         const __ROOTDELIM = this.delims.root + this.delims.delim;
                                         const __NOSCHEME = this.stripSchemePrefix(path);
                                         const __INDEXHOST = (__NOSCHEME ? __NOSCHEME.indexOf(__HOSTDELIM) : -1);
                                         const __PATH = ((~ __INDEXHOST) ? __NOSCHEME.substring(__INDEXHOST + __HOSTDELIM.length) : __NOSCHEME);
                                         const __INDEXHOSTPORT = (__PATH ? __PATH.indexOf(__ROOTDELIM) : -1);
                                         const __HOSTPORT = ((~ __INDEXHOSTPORT) ? __PATH.substring(0, __INDEXHOSTPORT) : undefined);
                                         const __INDEXPORT = (__HOSTPORT ? __HOSTPORT.indexOf(__PORTDELIM) : -1);
                                         const __HOST = ((~ __INDEXPORT) ? __HOSTPORT.substring(0, __INDEXPORT) : __HOSTPORT);
                                         const __PORT = ((~ __INDEXPORT) ? __HOSTPORT.substring(__INDEXPORT + __PORTDELIM.length) : undefined);

                                         return {
                                                    'host' : __HOST,
                                                    'port' : __PORT
                                                };
                                     },
               'stripHostPort'     : function(path = undefined) {
                                         const __HOSTDELIM = this.delims.host;
                                         const __ROOTDELIM = this.delims.root + this.delims.delim;
                                         const __INDEXHOST = (path ? path.indexOf(__HOSTDELIM) : -1);
                                         const __PATH = ((~ __INDEXHOST) ? path.substring(__INDEXHOST + __HOSTDELIM.length) : path);
                                         const __INDEXHOSTPORT = (__PATH ? __PATH.indexOf(__ROOTDELIM) : -1);

                                         return ((~ __INDEXHOSTPORT) ? __PATH.substring(__INDEXHOSTPORT) : __PATH);
                                     },
               'getSchemePrefix'   : function(path = undefined) {
                                         const __SCHEMEDELIM = this.delims.scheme;
                                         const __INDEXSCHEME = (path ? path.indexOf(__SCHEMEDELIM) : -1);

                                         return ((~ __INDEXSCHEME) ? path.substring(0, __INDEXSCHEME) : undefined);
                                     },
               'stripSchemePrefix' : function(path = undefined) {
                                         const __SCHEMEDELIM = this.delims.scheme;
                                         const __INDEXSCHEME = (path ? path.indexOf(__SCHEMEDELIM) : -1);

                                         return ((~ __INDEXSCHEME) ? path.substring(__INDEXSCHEME + __INDEXSCHEME.length) : path);
                                     },
               'getNodeSuffix'     : function(path = undefined) {
                                         const __NODEDELIM = this.delims.node;
                                         const __INDEXNODE = (path ? path.lastIndexOf(__NODEDELIM) : -1);

                                         return ((~ __INDEXNODE) ? path.substring(__INDEXNODE + __NODEDELIM.length) : undefined);
                                     },
               'stripNodeSuffix'   : function(path = undefined) {
                                         const __NODEDELIM = this.delims.node;
                                         const __INDEXNODE = (path ? path.lastIndexOf(__NODEDELIM) : -1);

                                         return ((~ __INDEXNODE) ? path.substring(0, __INDEXNODE) : path);
                                     },
               'getQueryString'    : function(path = undefined) {
                                         const __QUERYDELIM = this.delims.query;
                                         const __PATH = this.stripNodeSuffix(path);
                                         const __INDEXQUERY = (__PATH ? __PATH.indexOf(__QUERYDELIM) : -1);

                                         return ((~ __INDEXQUERY) ? __PATH.substring(__INDEXQUERY + __QUERYDELIM.length) : undefined);
                                     },
               'stripQueryString'  : function(path = undefined) {
                                         const __QUERYDELIM = this.delims.query;
                                         const __INDEXQUERY = (path ? path.indexOf(__QUERYDELIM) : -1);

                                         return ((~ __INDEXQUERY) ? path.substring(0, __INDEXQUERY) : path);
                                     },
               'formatParams'      : function(params, formatFun, delim = ' ', assign = '=') {
                                         const __PARAMS = [];

                                         for (let param in params) {
                                             __PARAMS.push(param + assign + formatFun(params[param]));
                                         }

                                         return __PARAMS.join(delim);
                                     },
               'parseParams'       : function(params, parseFun, delim = ' ', assign = '=') {
                                         const __RET = { };

                                         if (params) {
                                             const __PARAMS = params.split(delim);

                                             for (let index = 0; index < __PARAMS.length; index++) {
                                                 const __PARAM = __PARAMS[index];

                                                 if (__PARAM) {
                                                     const __INDEX = __PARAM.indexOf(assign);
                                                     const __KEY = ((~ __INDEX) ? __PARAM.substring(0, __INDEX) : __PARAM);
                                                     const __VAL = ((~ __INDEX) ? parseFun(__PARAM.substring(__INDEX + assign.length)) : true);

                                                     __RET[__KEY] = __VAL;
                                                 }
                                             }
                                         }

                                         return __RET;
                                     },
               'rawValue'          : function(value) {
                                         return value;
                                     },
               'parseValue'        : function(value) {
                                         const __VALUE = Number(value);

                                         if (__VALUE == value) {  // schwacher Vergleich true, also Number
                                             return __VALUE;
                                         } else {
                                             const __LOWER = (value ? value.toLowerCase() : undefined);

                                             if ((__LOWER === 'true') || (__LOWER === 'false')) {
                                                 return (value === 'true');
                                             }
                                         }

                                         return value;
                                     },
               'getQuery'          : function(delims = { }) {
                                         const __QRYSEP = ((delims.qrySep !== undefined) ? delims.qrySep : this.delims.qrySep);
                                         const __QRYASS = ((delims.qryAss !== undefined) ? delims.qryAss : this.delims.qryAss);

                                         return this.formatParams(this.query, this.rawValue, __QRYSEP, __QRYASS);
                                     },
               'parseQuery'        : function(path = undefined, delims = { }) {
                                         const __QRYSEP = ((delims.qrySep !== undefined) ? delims.qrySep : this.delims.qrySep);
                                         const __QRYASS = ((delims.qryAss !== undefined) ? delims.qryAss : this.delims.qryAss);

                                         return this.parseParams(path, this.parseValue, __QRYSEP, __QRYASS);
                                     },
               'setQuery'          : function(query) {
                                         this.query = query;
                                     },
               'setQueryPar'       : function(key, value) {
                                         this.query[key] = value;
                                     },
               'getQueryPar'       : function(key) {
                                         return this.query[key];
                                     },
               'getPath'           : function(dirs = undefined, delims = undefined) {
                                         const __DELIMS = new UriDelims(delims);
                                         const __SCHEMEDELIM = ((__DELIMS.scheme !== undefined) ? __DELIMS.scheme : this.delims.scheme);
                                         const __HOSTDELIM = ((__DELIMS.host !== undefined) ? __DELIMS.host : this.delims.host);
                                         const __PORTDELIM = ((__DELIMS.port !== undefined) ? __DELIMS.port : this.delims.port);
                                         const __QUERYDELIM = ((__DELIMS.query !== undefined) ? __DELIMS.query : this.delims.query);
                                         const __NODEDELIM = ((__DELIMS.node !== undefined) ? __DELIMS.node : this.delims.node);
                                         const __SCHEMENAME = this.scheme;
                                         const __SCHEME = (__SCHEMENAME ? __SCHEMENAME + __SCHEMEDELIM : "");
                                         const __HOSTNAME = this.host;
                                         const __HOST = (__HOSTNAME ? __HOSTDELIM + __HOSTNAME : "");
                                         const __PORTNR = this.port;
                                         const __PORT = ((__HOSTNAME && __PORTNR) ? __PORTDELIM + __PORTNR : "");
                                         const __QUERYSTR = this.getQuery();
                                         const __QUERY = (__QUERYSTR ? __QUERYDELIM + __QUERYSTR : "");
                                         const __NODENAME = this.node;
                                         const __NODE = (__NODENAME ? __NODEDELIM + __NODENAME : "");

                                         return __SCHEME + __HOST + __PORT + Path.prototype.getPath.call(this, dirs, delims) + __QUERY + __NODE;
                                     },
               'getDirs'           : function(path = undefined, delims = undefined) {
                                         const __PATH = this.getServerPath(path);

                                         return Path.prototype.getDirs.call(this, __PATH);
                                     }
           });

// ==================== Ende Abschnitt fuer Klasse URI ====================

// ==================== Abschnitt fuer Klasse Directory ====================

// Basisklasse fuer eine Verzeichnisstruktur
// homePath: Absoluter Startpfad als String
// delims: Objekt mit Trennern und Symbolen als Properties (oder Delims-Objekt)
// 'delim': Trennzeichen zwischen zwei Ebenen
// 'back': Name des relativen Vaterverzeichnisses
// 'root': Kennung vor dem ersten Trenner am Anfang eines absoluten Pfads
// 'home': Kennung vor dem ersten Trenner am Anfang eines Pfads relativ zu Home
function Directory(homePath, delims) {
    'use strict';

    Path.call(this, homePath, delims);
}

Class.define(Directory, Path, {
                    'chDir' : function(subDir = undefined) {
                                  if (subDir === undefined) {
                                      this.root();
                                  } else if ((typeof subDir) === 'object') {
                                      for (let sub of subDir) {
                                          this.chDir(sub);
                                      }
                                  } else {
                                      if (subDir === this.delims.home) {
                                          this.home();
                                      } else if (subDir === this.delims.back) {
                                          this.up();
                                      } else {
                                          this.down(subDir);
                                      }
                                  }
                              },
                    'pwd'   : function() {
                                  return this.getPath();
                              }
                });

// ==================== Ende Abschnitt fuer Klasse Directory ====================

// ==================== Abschnitt fuer Klasse ObjRef ====================

// Basisklasse fuer eine Objekt-Referenz
function ObjRef(rootObj) {
    'use strict';

    Directory.call(this, undefined, new Delims('/', "..", '/', '~'));

    this.setConst('rootObj', rootObj);  // Wichtig: Verweis nicht verfolgen! Gefahr durch Zyklen!
}

Class.define(ObjRef, Directory, {
                    'valueOf' : function() {
                                    let ret = this.rootObj;

                                    for (let name of this.dirs) {
                                        if (ret === undefined) {
                                            break;
                                        }
                                        ret = ret[name];
                                    }

                                    return ret;
                                }
                });

// ==================== Ende Abschnitt fuer Klasse ObjRef ====================

// ==================== Abschnitt fuer diverse Utilities ====================

// Gibt einen Wert zurueck. Ist dieser nicht definiert oder null, wird ein Alternativwert geliefert
// value: Ein Wert. Ist dieser nicht undefined oder null, wird er zurueckgeliefert (oder retValue)
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
// retValue: Falls definiert, Rueckgabe-Wert fuer den Fall, dass value nicht undefined oder null ist
// return Der Wert. Sind weder value noch defValue definiert, dann undefined
function getValue(value, defValue = undefined, retValue = undefined) {
    return ((value === undefined) || (value === null)) ? defValue : (retValue === undefined) ? value : retValue;
}

// Gibt einen Wert zurueck. Ist dieser nicht definiert, wird ein Alternativwert geliefert
// value: Ein Wert. Ist dieser definiet und in den Grenzen, wird er zurueckgeliefert
// minValue: Untere Grenze fuer den Wert, falls angegeben
// minValue: Obere Grenze fuer den Wert, falls angegeben
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist oder der Wert ausserhalb liegt
// return Der Wert. Sind weder value (in den Grenzen) noch defValue definiert, dann undefined
function getValueIn(value, minValue = undefined, maxValue = undefined, defValue = undefined) {
    const __VALUE = getValue(value, defValue);

    if ((minValue !== undefined) && (__VALUE < minValue)) {
        return defValue;
    }
    if ((maxValue !== undefined) && (__VALUE > maxValue)) {
        return defValue;
    }

    return __VALUE;
}

// Ermittelt den naechsten Wert aus einer Array-Liste
// arr: Array-Liste mit den moeglichen Werte
// value: Vorher gesetzter Wert
// return Naechster Wert in der Array-Liste
function getNextValue(arr, value) {
    const __POS = arr.indexOf(value) + 1;

    return arr[getValueIn(__POS, 0, arr.length - 1, 0)];
}

// Gibt ein Produkt zurueck. Ist einer der Multiplikanten nicht definiert, wird ein Alternativwert geliefert
// valueA: Ein Multiplikant. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
// valueB: Ein Multiplikant. Ist dieser undefined, wird als Produkt defValue zurueckgeliefert
// digits: Anzahl der Stellen nach dem Komma fuer das Produkt (Default: 0)
// defValue: Default-Wert fuer den Fall, dass ein Multiplikant nicht gesetzt ist (Default: NaN)
// return Das Produkt auf digits Stellen genau. Ist dieses nicht definiert, dann defValue
function getMulValue(valueA, valueB, digits = 0, defValue = NaN) {
    let product = defValue;

    if ((valueA !== undefined) && (valueB !== undefined)) {
        product = parseFloat(valueA) * parseFloat(valueB);
    }

    if (isNaN(product)) {
        product = defValue;
    }

    return parseFloat(product.toFixed(digits));
}

// Gibt eine Ordinalzahl zur uebergebenen Zahl zurueck
// value: Eine ganze Zahl
// defValue: Default-Wert fuer den Fall, dass der Wert nicht gesetzt ist (Default: '*')
// return Die Ordinalzahl als String der Form "n." oder defValue, falls nicht angegeben
function getOrdinal(value, defValue = '*') {
    return getValue(value, defValue, value + '.');
}

// Hilfsfunktion fuer Array.sort(): Vergleich zweier Zahlen
// valueA: Erster Zahlenwert
// valueB: Zweiter Zahlenwert
// return -1 = kleiner, 0 = gleich, +1 = groesser
function compareNumber(valueA, valueB) {
    return +(valueA > valueB) || (+(valueA === valueB) - 1);
}

// Ueberprueft, ob ein Objekt einer bestimmten Klasse angehoert (ggfs. per Vererbung)
// obj: Ein (generisches) Objekt
// base: Eine Objektklasse (Konstruktor-Funktion)
// return true, wenn der Prototyp rekursiv gefunden werden konnte
function instanceOf(obj, base) {
    while (obj !== null) {
        if (obj === base.prototype) {
            return true;
        }
        if ((typeof obj) === 'xml') {  // Sonderfall mit Selbstbezug
            return (base.prototype === XML.prototype);
        }
        obj = Object.getPrototypeOf(obj);
    }

    return false;
}

// Liefert alle Basisklassen des Objekts (inkl. Vererbung)
// obj: Ein (generisches) Objekt
// return true, wenn der Prototyp rekursiv gefunden werden konnte
function getPrototypes(obj) {
    let ret = [];

    while (obj !== null) {
        const __PROTO = Object.getPrototypeOf(obj);

        ret.push(__PROTO);
        if ((typeof obj) === 'xml') {  // Sonderfall mit Selbstbezug
            break;
        }
        obj = __PROTO;
    }

    return ret;
}

// Liefert alle Attribute/Properties des Objekts (inkl. Vererbung)
// obj: Ein (generisches) Objekt
// return Array von Items (Property-Namen)
function getAllProperties(obj) {
    let ret = [];

    for (let o = obj; o !== null; o = Object.getPrototypeOf(o)) {
      ret = ret.concat(Object.getOwnPropertyNames(o));
    }

    return ret;
}

// Ueberpruefung, ob ein Item aktiv ist oder nicht
// item: Name des betroffenen Items
// inList: Checkliste der inkludierten Items (Positivliste, true fuer aktiv)
// exList: Checkliste der exkludierten Items (Negativliste, true fuer inaktiv)
// return Angabe, ob das Item aktiv ist
function checkItem(item, inList = undefined, exList = undefined) {
    let active = true;

    if (inList) {
        active = (inList[item] === true);  // gesetzt und true
    }
    if (exList) {
        if (exList[item] === true) {  // gesetzt und true
            active = false;  // NICHT anzeigen
        }
    }

    return active;
}

// Fuegt Properties zu einem Objekt hinzu, die in einem zweiten stehen. Doppelte Werte werden ueberschrieben
// data: Objekt, dem Daten hinzugefuegt werden
// addData: Objekt, das zusaetzliche Properties enthaelt
// addList: Checkliste der zu setzenden Items (true fuer kopieren), falls angegeben
// ignList: Checkliste der ignorierten Items (true fuer auslassen), falls angegeben
// return Das gemergete Objekt mit allen Properties
function addProps(data, addData, addList = undefined, ignList = undefined) {
    for (let item in getValue(addData, { })) {
        if (checkItem(item, addList, ignList)) {
            data[item] = addData[item];
        }
    }

    return data;
}

// Entfernt Properties in einem Objekt
// data: Objekt, deren Properties bearbeitet werden
// delList: Checkliste der zu loeschenden Items (true fuer loeschen), falls angegeben
// ignList: Checkliste der ignorierten Items (true fuer auslassen), falls angegeben
// return Das veraenderte Objekt ohne die geloeschten Properties
function delProps(data, delList = undefined, ignList = undefined) {
    for (let item in getValue(data, { })) {
        if (checkItem(item, delList, ignList)) {
            delete data[item];
        }
    }

    return data;
}

// Gibt den Wert einer Property zurueck. Ist dieser nicht definiert oder null, wird er vorher gesetzt
// obj: Ein Objekt. Ist dieses undefined oder null, wird defValue zurueckgeliefert
// item: Key des Properties
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
// return Der Wert des Properties. Sind das obj oder das Property und defValue undefined oder null, dann undefined
function getProp(obj, item, defValue = undefined) {
    if ((obj === undefined) || (obj === null)) {
        return defValue;
    }

    const __PROP = obj[item];

    if ((__PROP !== undefined) && (__PROP !== null)) {
        return __PROP;
    }

    return (obj[item] = defValue);
}

// Sicheres obj.valueOf() fuer alle Daten
// data: Objekt oder Wert
// return Bei Objekten valueOf() oder das Objekt selber, bei Werten der Wert
function valueOf(data) {
    return (((typeof data) === 'object') ? data.valueOf() : data);
}

// Sicheres JSON.stringify(), das auch mit Zyklen umgehen kann
// value: Auszugebene Daten. Siehe JSON.stringify()
// replacer: Elementersetzer. Siehe JSON.stringify()
// space: Verschoenerung. Siehe JSON.stringify()
// cycleReplacer: Ersetzer im Falle von Zyklen
// return String mit Ausgabe der Objektdaten
function safeStringify(value, replacer = undefined, space = undefined, cycleReplacer = undefined) {
    return JSON.stringify(value, serializer(replacer, cycleReplacer), space);
}

// Hilfsfunktion fuer safeStringify(): Kapselt replacer und einen cycleReplacer fuer Zyklen
// replacer: Elementersetzer. Siehe JSON.stringify()
// cycleReplacer: Ersetzer im Falle von Zyklen
// return Ersetzer-Funktion fuer JSON.stringify(), die beide Ersetzer vereint
function serializer(replacer = undefined, cycleReplacer = undefined) {
    const __STACK = [];
    const __KEYS = [];

    if (! cycleReplacer) {
        cycleReplacer = function(key, value) {
                if (__STACK[0] === value) {
                    return "[~]";
                }
                return "[~." + __KEYS.slice(0, __STACK.indexOf(value)).join('.') + ']';
            };
    }

    return function(key, value) {
            if (__STACK.length) {
                const __THISPOS = __STACK.indexOf(this);

                if (~ __THISPOS) {
                    __STACK.splice(__THISPOS + 1);
                    __KEYS.splice(__THISPOS, Infinity, key);
                } else {
                    __STACK.push(this);
                    __KEYS.push(key);
                }
                if (~ __STACK.indexOf(value)) {
                    value = cycleReplacer.call(this, key, value);
                }
            } else {
                __STACK.push(value);
            }

            return ((! replacer) ? value : replacer.call(this, key, value));
        };
}

// Replacer fuer JSON.stringify() oder safeStringify(), der Arrays kompakter darstellt
// key: Der uebergebene Schluessel
// value: Der uebergebene Wert
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
function replaceArraySimple(key, value) {
    if (Array.isArray(value)) {
        return "[ " + value.join(", ") + " ]";
    }

    return value;
}

// Replacer fuer JSON.stringify() oder safeStringify(), der Arrays kompakter darstellt
// key: Der uebergebene Schluessel
// value: Der uebergebene Wert
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
function replaceArray(key, value) {
    if (Array.isArray(value)) {
        __RET = value.map(function(element) {
                              return safeStringify(element, replaceArray, 0);
                          });

        return __RET;
    }

    return value;
}

// Moegliche einfache Ersetzungen mit '$'...
let textSubst;

// Substituiert '$'-Parameter in einem Text
// text: Urspruenglicher Text mit '$'-Befehlen
// par1: Der (erste) uebergebene Parameter
// return Fuer Arrays eine kompakte Darstellung, sonst derselbe Wert
function substParam(text, par1) {
    let ret = getValue(text, "");

    if (! textSubst) {
        textSubst  = {
                'n' : __DBMOD.name,
                'v' : __DBMOD.version,
                'V' : __DBMOD.Name
            };
    }

    for (let ch in textSubst) {
        const __SUBST = textSubst[ch];

        ret = ret.replace('$' + ch, __SUBST);
    }

    return ret.replace('$', par1);
}

// Fuegt in die uebergebene Zahl Tausender-Trennpunkte ein
// Wandelt einen etwaig vorhandenen Dezimalpunkt in ein Komma um
// numberString: Dezimalzahl als String
// return Diese Dezimalzahl als String mit Tausender-Trennpunkten und Komma statt Dezimalpunkt
function getNumberString(numberString) {
    if (numberString.lastIndexOf('.') !== -1) {
        // Zahl enthaelt Dezimalpunkt
        const __VORKOMMA = numberString.substring(0, numberString.lastIndexOf('.'));
        const __NACHKOMMA = numberString.substring(numberString.lastIndexOf('.') + 1, numberString.length);

        return getNumberString(__VORKOMMA) + ',' + __NACHKOMMA;
    } else {
        // Kein Dezimalpunkt, fuege Tausender-Trennpunkte ein:
        // String umdrehen, nach jedem dritten Zeichen Punkt einfuegen, dann wieder umdrehen:
        const __TEMP = reverseString(numberString);
        let result = "";

        for (let i = 0; i < __TEMP.length; i++) {
            if ((i > 0) && (i % 3 === 0)) {
                result += '.';
            }
            result += __TEMP.substr(i, 1);
        }

        return reverseString(result);
    }
}

// Dreht den uebergebenen String um
// string: Eine Zeichenkette
// return Dieselbe Zeichenkette rueckwaerts
function reverseString(string) {
    let result = "";

    for (let i = string.length - 1; i >= 0; i--) {
        result += string.substr(i, 1);
    }

    return result;
}

// Speichert einen String/Integer/Boolean-Wert unter einem Namen ab
// name: GM.setValue()-Name, unter dem die Daten gespeichert werden
// value: Zu speichernder String/Integer/Boolean-Wert
// return Promise auf ein Objekt, das 'name' und 'value' der Operation enthaelt
function storeValue(name, value) {
    __LOG[4](name + " >> " + value);

    return GM.setValue(name, value).then(voidValue => {
            __LOG[5]("OK " + name + " >> " + value);

            return Promise.resolve({
                    'name'  : name,
                    'value' : value
                });
        }, defaultCatch);
}

// Holt einen String/Integer/Boolean-Wert unter einem Namen zurueck
// name: GM.getValue()-Name, unter dem die Daten gespeichert wurden
// defValue: Default-Wert fuer den Fall, dass nichts gespeichert ist
// return Promise fuer den String/Integer/Boolean-Wert, der unter dem Namen gespeichert war
function summonValue(name, defValue = undefined) {
    return GM.getValue(name, defValue).then(value => {
            __LOG[4](name + " << " + value);

            return Promise.resolve(value);
        }, ex => {
            __LOG[0](name + ": " + ex.message);

            return Promise.reject(ex);
        }, defaultCatch);
}

// Speichert einen beliebiegen (strukturierten) Wert unter einem Namen ab
// name: GM.setValue()-Name, unter dem die Daten gespeichert werden
// value: Beliebiger (strukturierter) Wert
// return Promise auf ein Objekt, das 'name' und 'value' in der String-Darstellung des Wertes enthaelt
function serialize(name, value) {
    const __STREAM = ((value !== undefined) ? safeStringify(value) : value);

    return storeValue(name, __STREAM);
}

// Holt einen beliebiegen (strukturierter) Wert unter einem Namen zurueck
// name: GM.getValue()-Name, unter dem die Daten gespeichert wurden
// defValue: Default-Wert fuer den Fall, dass nichts gespeichert ist
// return Promise fuer das Objekt, das unter dem Namen gespeichert war
function deserialize(name, defValue = undefined) {
    return summonValue(name).then(stream => {
            if (stream && stream.length) {
                return JSON.parse(stream);
            } else {
                return defValue;
            }
        });
}

// Setzt die Seite gemaess der Aenderungen zurueck...
// reload: Seite wird ganz neu geladen
function refreshPage(reload = true) {
    if (reload) {
        __LOG[2]("Seite wird neu geladen...");
        window.location.reload();
    }
}

// Setzt eine Option dauerhaft und laedt die Seite neu
// name: Name der Option als Speicherort
// value: Zu setzender Wert
// reload: Seite mit neuem Wert neu laden
// serial: Serialization fuer komplexe Daten
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gespeicherter Wert fuer setOptValue()
function setStored(name, value, reload = false, serial = false, onFulfilled = undefined, onRejected = undefined) {
    (serial ? serialize(name, value)
            : storeValue(name, value))
                .then(onFulfilled, onRejected)
                .then(() => refreshPage(reload), defaultCatch);  // Ende der Kette...

    return value;
}

// Setzt den naechsten Wert aus einer Array-Liste als Option
// arr: Array-Liste mit den moeglichen Optionen
// name: Name der Option als Speicherort
// value: Vorher gesetzter Wert
// reload: Seite mit neuem Wert neu laden
// serial: Serialization fuer komplexe Daten
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gespeicherter Wert fuer setOptValue()
function setNextStored(arr, name, value, reload = false, serial = false, onFulfilled = undefined, onRejected = undefined) {
    return setStored(name, getNextValue(arr, value), reload, serial, onFulfilled, onRejected);
}

// Fuehrt die in einem Storage gespeicherte Operation aus
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return Array von Objekten mit 'cmd' / 'key' / 'val' (derzeit maximal ein Kommando) oder undefined
function getStoredCmds(memory = undefined) {
    const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;
    const __RUNPREFIX = __STORAGE.Prefix;
    const __STOREDCMDS = [];

    if (__MEMORY !== undefined) {
        const __GETITEM = function(item) {
                              return __MEMORY.getItem(__RUNPREFIX + item);
                          };
        const __DELITEM = function(item) {
                              return __MEMORY.removeItem(__RUNPREFIX + item);
                          };
        const __CMD = ((__MEMORY !== undefined) ? __GETITEM('cmd') : undefined);

        if (__CMD !== undefined) {
            const __KEY = __GETITEM('key');
            let value = __GETITEM('val');

            try {
                value = JSON.parse(value);
            } catch (ex) {
                __LOG[0]("getStoredCmds(): " + __CMD + " '" + __KEY + "' hat illegalen Wert '" + value + "'");
                // ... meist kann man den String selber aber speichern, daher kein "return"...
            }

            __STOREDCMDS.push({
                                'cmd' : __CMD,
                                'key' : __KEY,
                                'val' : value
                            });
        }

        __DELITEM('cmd');
        __DELITEM('key');
        __DELITEM('val');
    }

    return (__STOREDCMDS.length ? __STOREDCMDS : undefined);
}

// Fuehrt die in einem Storage gespeicherte Operation aus
// storedCmds: Array von Objekten mit 'cmd' / 'key' / 'val' (siehe getStoredCmds())
// optSet: Object mit den Optionen
// beforeLoad: Angabe, ob nach der Speicherung noch loadOptions() aufgerufen wird
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Promise auf ein Array von Operationen (wie storedCmds), die fuer die naechste Phase uebrig bleiben
async function runStoredCmds(storedCmds, optSet = undefined, beforeLoad = undefined, onFulfilled = undefined, onRejected = undefined) {
    const __BEFORELOAD = getValue(beforeLoad, true);
    const __STOREDCMDS = getValue(storedCmds, []);
    const __LOADEDCMDS = [];
    let invalidated = false;

    while (__STOREDCMDS.length) {
        const __STORED = __STOREDCMDS.shift();
        const __CMD = __STORED.cmd;
        const __KEY = __STORED.key;
        const __VAL = __STORED.val;

        if (__BEFORELOAD) {
            if (__STOREDCMDS.length) {
                await invalidateOpts(optSet);  // alle Optionen invalidieren
                invalidated = true;
            }
            switch (__OPTACTION[__CMD]) {
            case __OPTACTION.SET : __LOG[4]("SET '" + __KEY + "' " + __VAL);
                                   setStored(__KEY, __VAL, false, false, onFulfilled, onRejected);
                                   break;
            case __OPTACTION.NXT : __LOG[4]("SETNEXT '" + __KEY + "' " + __VAL);
                                   //setNextStored(__CONFIG.Choice, __KEY, __VAL, false, false, onFulfilled, onRejected);
                                   setStored(__KEY, __VAL, false, false, onFulfilled, onRejected);
                                   break;
            case __OPTACTION.RST : __LOG[4]("RESET (delayed)");
                                   __LOADEDCMDS.push(__STORED);
                                   break;
            default :              break;
            }
        } else {
            switch (__OPTACTION[__CMD]) {
            case __OPTACTION.SET :
            case __OPTACTION.NXT : __LOG[2]("SET/SETNEXT (undefined)");
                                   break;
            case __OPTACTION.RST : __LOG[4]("RESET");
                                   await resetOptions(optSet, false);
                                   await loadOptions(optSet);  // Reset auf umbenannte Optionen anwenden!
                                   break;
            default :              break;
            }
        }
    }

    return (__LOADEDCMDS.length ? __LOADEDCMDS : undefined);
}

// Gibt eine Option sicher zurueck
// opt: Config und Value der Option, ggfs. undefined
// defOpt: Rueckgabewert, falls undefined
// return Daten zur Option (oder defOpt)
function getOpt(opt, defOpt = { }) {
    return getValue(opt, defOpt);
}

// Gibt eine Option sicher zurueck (Version mit Key)
// optSet: Platz fuer die gesetzten Optionen (und Config)
// item: Key der Option
// defOpt: Rueckgabewert, falls nicht zu finden
// return Daten zur Option (oder defOpt)
function getOptByName(optSet, item, defOpt = { }) {
    if ((optSet !== undefined) && (item !== undefined)) {
        return getOpt(optSet[item], defOpt);
    } else {
        return defOpt;
    }
}

// Gibt die Konfigurationsdaten einer Option zurueck
// opt: Config und Value der Option
// defConfig: Rueckgabewert, falls Config nicht zu finden
// return Konfigurationsdaten der Option
function getOptConfig(opt, defConfig = { }) {
    return getValue(getOpt(opt).Config, defConfig);
}

// Setzt den Namen einer Option
// opt: Config und Value der Option
// name: Zu setzender Name der Option
// reload: Seite mit neuem Wert neu laden
// return Gesetzter Name der Option
function setOptName(opt, name) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);

    if (__NAME !== name) {
        __LOG[4]("RENAME " + __NAME + " => " + name);

        __CONFIG.Name = name;
    }

    return name;
}

// Gibt den Namen einer Option zurueck
// opt: Config und Value der Option
// return Name der Option
function getOptName(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = __CONFIG.Name;

    if (! __NAME) {
        const __SHARED = __CONFIG.Shared;

        if (__SHARED && ! opt.Loaded) {
            const __OBJREF = getSharedRef(__SHARED, opt.Item);

            return __OBJREF.getPath();
        }

        showAlert("Error", "Option ohne Namen", safeStringify(__CONFIG));
    }

    return __NAME;
}

// Setzt den Wert einer Option
// opt: Config und Value der Option
// name: Zu setzender Wert der Option
// return Gesetzter Wert
function setOptValue(opt, value) {
    if (opt !== undefined) {
        if (! opt.ReadOnly) {
            __LOG[6](getOptName(opt) + ": " + __LOG.changed(opt.Value, value));

            opt.Value = value;
        }
        return opt.Value;
    } else {
        return undefined;
    }
}

// Gibt den Wert einer Option zurueck
// opt: Config und Value der Option
// defValue: Default-Wert fuer den Fall, dass nichts gesetzt ist
// load: Laedt die Option per loadOption(), falls noetig
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Gesetzter Wert
function getOptValue(opt, defValue = undefined, load = true, asyncLoad = false, force = false) {
    let value;

    if (opt !== undefined) {
        if (load && ! opt.Loaded) {
            if (! opt.Promise) {
                loadOption(opt, force);
            }
            if (! asyncLoad) {
                __LOG[0]("Warnung: getOptValue(" + getOptName(opt) + ") fordert zum Nachladen auf, daher nur Default-Wert!");
            }
        } else {
            value = opt.Value;
        }
    }

    return valueOf(getValue(value, defValue));
}

// ==================== Ende Abschnitt fuer diverse Utilities ====================

// ==================== Abschnitt fuer Speicher und die Scriptdatenbank ====================

// Namen des Default-, Temporaer- und Null-Memories...
const __MEMNORMAL   = 'normal';
const __MEMSESSION  = 'begrenzt';
const __MEMINAKTIVE = 'inaktiv';

// Definition des Default-, Dauer- und Null-Memories...
const __OPTMEMNORMAL   = __OPTMEM[__MEMNORMAL];
const __OPTMEMSESSION  = __OPTMEM[__MEMSESSION];
const __OPTMEMINAKTIVE = __OPTMEM[__MEMINAKTIVE];

// Medium fuer die Datenbank (Speicher)
let myOptMem = __OPTMEMNORMAL;
let myOptMemSize;

// Infos ueber dieses Script-Modul
const __DBMOD = new ScriptModule();

// Inhaltsverzeichnis der DB-Daten (indiziert durch die Script-Namen)
const __DBTOC = { };

// Daten zu den Modulen (indiziert durch die Script-Namen)
const __DBDATA = { };

// ==================== Abschnitt fuer Speicher ====================

// Ermittelt fuer die uebergebene Speicher-Konfiguration einen Speicher
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// defMemory: Ersatz-Wert, falls memory undefined. Soll nur memory genutzt werden, dann z.B. null uebergeben!
// return memory, falls okay, sonst einen Defaultwert
function getMemory(memory = undefined, defMemory = getValue(myOptMem, __OPTMEMNORMAL)) {
    return getValue(memory, defMemory);
}

// Kompatibilitaetsfunktion: Testet, ob der uebergebene Speicher genutzt werden kann
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return true, wenn der Speichertest erfolgreich war
function canUseMemory(memory = undefined) {
    const __STORAGE = getMemory(memory, { });
    const __MEMORY = __STORAGE.Value;
    let ret = false;

    if (__MEMORY !== undefined) {
        const __TESTPREFIX = 'canUseStorageTest';
        const __TESTDATA = Math.random().toString();
        const __TESTITEM = __TESTPREFIX + __TESTDATA;

        __MEMORY.setItem(__TESTITEM, __TESTDATA);
        ret = (__MEMORY.getItem(__TESTITEM) === __TESTDATA);
        __MEMORY.removeItem(__TESTITEM);
    }

    __LOG[2]("canUseStorage(" + __STORAGE.Name + ") = " + ret);

    return ret;
}

// Ermittelt die Groesse des benutzten Speichers
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return Groesse des genutzten Speichers in Bytes
function getMemSize(memory = undefined) {
    const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;

    //getMemUsage(__MEMORY);

    if (__MEMORY !== undefined) {
        const __SIZE = safeStringify(__MEMORY).length;

        __LOG[2]("MEM: " + __SIZE + " bytes");
        return __SIZE;
    } else {
        return 0;
    }
}

// Gibt rekursiv und detailliert die Groesse des benutzten Speichers fuer ein Objekt aus
// value: (Enumerierbares) Objekt oder Wert, dessen Groesse gemessen wird
// out: Logfunktion, etwa __LOG[4]
// depth: Gewuenschte Rekursionstiefe (0 = nur dieses Objekt, -1 = alle Ebenen)
// name: Name des Objekts
function getMemUsage(value = undefined, out = undefined, depth = -1, name = '$') {
    const __OUT = (out || __LOG[4]);

    if ((typeof value) === 'string') {
        const __SIZE = value.length;

        __OUT("USAGE: " + name + '\t' + __SIZE + '\t' + value.slice(0, 255));
    } else if ((typeof value) === 'object') {
        if (depth === 0) {
            const __SIZE = safeStringify(value).length;

            __OUT("USAGE: " + name + '\t' + __SIZE);
        } else {
            depth--;
            for (let sub in value) {
                getMemUsage(value[sub], __OUT, depth, name + '.' + sub);
            }
            getMemUsage(value, __OUT, 0, name);
        }
    } else {
       const __DATA = (((typeof value) === 'function') ? "" : '\t' + value);

        __OUT("USAGE: " + name + '\t' + (typeof value) + __DATA);
    }
}

// Restauriert den vorherigen Speicher (der in einer Option definiert ist)
// opt: Option zur Wahl des Speichers
// return Promise auf gesuchten Speicher oder Null-Speicher ('inaktiv')
async function restoreMemoryByOpt(opt) {
    // Memory Storage fuer vorherige Speicherung...
    const __STORAGE = await getOptValue(opt, __MEMNORMAL, true, true, true);

    return __OPTMEM[__STORAGE];
}

// Initialisiert den Speicher (der in einer Option definiert ist) und merkt sich diesen ggfs.
// opt: Option zur Wahl des Speichers
// saveOpt: Option zur Speicherung der Wahl des Speichers (fuer restoreMemoryByOpt)
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesuchter Speicher oder Null-Speicher ('inaktiv'), falls speichern nicht moeglich ist
function startMemoryByOpt(opt, saveOpt = undefined, onFulfilled = undefined, onRejected = undefined) {
    // Memory Storage fuer naechste Speicherung...
    let storage = getOptValue(opt, __MEMNORMAL);
    let optMem = __OPTMEM[storage];

    if (! canUseMemory(optMem)) {
        if (storage !== __MEMINAKTIVE) {
            storage = __MEMINAKTIVE;
            optMem = __OPTMEM[storage];
        }
    }

    if (saveOpt !== undefined) {
        setOpt(saveOpt, storage, false, onFulfilled, onRejected);
    }

    return optMem;
}

// ==================== Ende Abschnitt fuer Speicher ====================

// ==================== Abschnitt fuer die Scriptdatenbank ====================

// Initialisiert das Script-Modul und ermittelt die beschreibenden Daten
// meta: Metadaten des Scripts (Default: GM.info.script)
// return Beschreibende Daten fuer __DBMOD
function ScriptModule(meta) {
    'use strict';

    const __META = getValue(meta, GM.info.script);
    const __PROPS = {
                'name'        : true,
                'version'     : true,
                'namespace'   : true,
                'description' : true
            };

    const __DBMOD = { };

    __LOG[5](__META);

    // Infos zu diesem Script...
    addProps(__DBMOD, __META, __PROPS);

    // Voller Name fuer die Ausgabe...
    Object.defineProperty(__DBMOD, 'Name', {
                    get : function() {
                              return this.name + " (" + this.version + ')';
                          },
                    set : undefined
                });

    __LOG[2](__DBMOD);

    return __DBMOD;
}

Class.define(ScriptModule, Object);

// Initialisiert die Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
// optSet: Gesetzte Optionen (und Config)
function initScriptDB(optSet) {
     // Speicher fuer die DB-Daten...
    const __DBMEM = myOptMem.Value;

    __DBTOC.versions = getValue((__DBMEM === undefined) ? undefined : JSON.parse(__DBMEM.getItem('__DBTOC.versions')), { });
    __DBTOC.namespaces = getValue((__DBMEM === undefined) ? undefined : JSON.parse(__DBMEM.getItem('__DBTOC.namespaces')), { });

    // Zunaechst den alten Eintrag entfernen...
    delete __DBTOC.versions[__DBMOD.name];
    delete __DBTOC.namespaces[__DBMOD.name];

    if (__DBMEM !== undefined) {
        // ... und die Daten der Fremdscripte laden...
        for (let module in __DBTOC.versions) {
            scriptDB(module, getValue(JSON.parse(__DBMEM.getItem('__DBDATA.' + module)), { }));
        }
    }
}

// Setzt die Daten dieses Scriptes in der Scriptdatenbank, die einen Datenaustausch zwischen den Scripten ermoeglicht
// optSet: Gesetzte Optionen (und Config)
function updateScriptDB(optSet) {
    // Eintrag ins Inhaltsverzeichnis...
    __DBTOC.versions[__DBMOD.name] = __DBMOD.version;
    __DBTOC.namespaces[__DBMOD.name] = __DBMOD.namespace;

    // Speicher fuer die DB-Daten...
    const __DBMEM = myOptMem.Value;

    if (__DBMEM !== undefined) {
        // Permanente Speicherung der Eintraege...
        __DBMEM.setItem('__DBTOC.versions', safeStringify(__DBTOC.versions));
        __DBMEM.setItem('__DBTOC.namespaces', safeStringify(__DBTOC.namespaces));
        __DBMEM.setItem('__DBDATA.' + __DBMOD.name, safeStringify(optSet));

        // Aktualisierung der Speichergroesse...
        myOptMemSize = getMemSize(myOptMem);
    }

    // Jetzt die inzwischen gefuellten Daten *dieses* Scripts ergaenzen...
    scriptDB(__DBMOD.name, getValue(optSet, { }));

    __LOG[2](__DBDATA);
}

// Holt die globalen Daten zu einem Modul aus der Scriptdatenbank
// module: Gesetzte Optionen (und Config)
// initValue: Falls angegeben, zugewiesener Startwert
// return Daten zu diesem Modul
function scriptDB(module, initValue = undefined) {
    const __NAMESPACE = __DBTOC.namespaces[module];
    const __DBMODS = getProp(__DBDATA, __NAMESPACE, { });

    if (initValue !== undefined) {
        return (__DBMODS[module] = initValue);
    } else {
        return getProp(__DBMODS, module, { });
    }
}

// ==================== Ende Abschnitt fuer die Scriptdatenbank ====================

// ==================== Ende Abschnitt fuer Speicher und die Scriptdatenbank ====================

// ==================== Abschnitt fuer das Benutzermenu ====================

// Zeigt den Eintrag im Menu einer Option
// val: Derzeitiger Wert der Option
// menuOn: Text zum Setzen im Menu
// funOn: Funktion zum Setzen
// keyOn: Hotkey zum Setzen im Menu
// menuOff: Text zum Ausschalten im Menu
// funOff: Funktion zum Ausschalten
// keyOff: Hotkey zum Ausschalten im Menu
// return Promise von GM.registerMenuCommand()
function registerMenuOption(val, menuOn, funOn, keyOn, menuOff, funOff, keyOff) {
    const __ON  = (val ? '*' : "");
    const __OFF = (val ? "" : '*');

    __LOG[3]("OPTION " + __ON + menuOn + __ON + " / " + __OFF + menuOff + __OFF);

    if (val) {
        return GM.registerMenuCommand(menuOff, funOff, keyOff).then(result => menuOn);
    } else {
        return GM.registerMenuCommand(menuOn, funOn, keyOn).then(result => menuOff);
    }
}

// Zeigt den Eintrag im Menu einer Option mit Wahl des naechsten Wertes
// val: Derzeitiger Wert der Option
// arr: Array-Liste mit den moeglichen Optionen
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
// fun: Funktion zum Setzen des naechsten Wertes
// key: Hotkey zum Setzen des naechsten Wertes im Menu
// return Promise von GM.registerMenuCommand()
function registerNextMenuOption(val, arr, menu, fun, key) {
    const __MENU = substParam(menu, val);
    let options = "OPTION " + __MENU;

    for (let value of arr) {
        if (value === val) {
            options += " / *" + value + '*';
        } else {
            options += " / " + value;
        }
    }
    __LOG[3](options);

    return GM.registerMenuCommand(__MENU, fun, key).then(result => __MENU);
}

// Zeigt den Eintrag im Menu einer Option, falls nicht hidden
// val: Derzeitiger Wert der Option
// menu: Text zum Setzen im Menu ($ wird durch gesetzten Wert ersetzt)
// fun: Funktion zum Setzen des naechsten Wertes
// key: Hotkey zum Setzen des naechsten Wertes im Menu
// hidden: Angabe, ob Menupunkt nicht sichtbar sein soll (Default: sichtbar)
// serial: Serialization fuer komplexe Daten
// return Promise von GM.registerMenuCommand() (oder String-Version des Wertes)
function registerDataOption(val, menu, fun, key, hidden = false, serial = true) {
    const __VALUE = ((serial && (val !== undefined)) ? safeStringify(val) : val);
    const __MENU = substParam(menu, __VALUE);
    const __OPTIONS = (hidden ? "HIDDEN " : "") + "OPTION " + __MENU +
                      getValue(__VALUE, "", " = " + __VALUE);

    __LOG[hidden ? 4 : 3](__OPTIONS);

    if (hidden) {
        return Promise.resolve(__VALUE);
    } else {
        return GM.registerMenuCommand(__MENU, fun, key).then(result => __MENU);
    }
}

// Zeigt den Eintrag im Menu einer Option
// opt: Config und Value der Option
// return Promise von GM.registerMenuCommand() (oder String-Version des Wertes)
function registerOption(opt) {
    const __CONFIG = getOptConfig(opt);
    const __VALUE = getOptValue(opt);
    const __LABEL = __CONFIG.Label;
    const __ACTION = opt.Action;
    const __HOTKEY = __CONFIG.Hotkey;
    const __HIDDEN = __CONFIG.HiddenMenu;
    const __SERIAL = __CONFIG.Serial;

    if (! __CONFIG.HiddenMenu) {
        switch (__CONFIG.Type) {
        case __OPTTYPES.MC : return registerNextMenuOption(__VALUE, __CONFIG.Choice, __LABEL, __ACTION, __HOTKEY);
        case __OPTTYPES.SW : return registerMenuOption(__VALUE, __LABEL, __ACTION, __HOTKEY,
                                                       __CONFIG.AltLabel, __ACTION, __CONFIG.AltHotkey);
        case __OPTTYPES.TF : return registerMenuOption(__VALUE, __LABEL, __ACTION, __HOTKEY,
                                                       __CONFIG.AltLabel, opt.AltAction, __CONFIG.AltHotkey);
        case __OPTTYPES.SD : return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
        case __OPTTYPES.SI : return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
        default :            return Promise.resolve(__VALUE);
        }
    } else {
        // Nur Anzeige im Log...
        return registerDataOption(__VALUE, __LABEL, __ACTION, __HOTKEY, __HIDDEN, __SERIAL);
    }
}

// ==================== Ende Abschnitt fuer das Benutzermenu ====================

// Initialisiert die gesetzten Option
// config: Konfiguration der Option
// setValue: Zu uebernehmender Default-Wert (z.B. der jetzt gesetzte)
// return Initialwert der gesetzten Option
function initOptValue(config, setValue = undefined) {
    let value = getValue(setValue, config.Default);  // Standard

    if (config.SharedData !== undefined) {
        value = config.SharedData;
    }

    switch (config.Type) {
    case __OPTTYPES.MC : if ((value === undefined) && (config.Choice !== undefined)) {
                             value = config.Choice[0];
                         }
                         break;
    case __OPTTYPES.SW : break;
    case __OPTTYPES.TF : break;
    case __OPTTYPES.SD : config.Serial = true;
                         break;
    case __OPTTYPES.SI : break;
    default :            break;
    }

    if (config.Serial || config.Hidden) {
        config.HiddenMenu = true;
    }

    return value;
}

// Initialisiert die Menue-Funktion einer Option
// optAction: Typ der Funktion
// item: Key der Option
// optSet: Platz fuer die gesetzten Optionen (und Config)
// optConfig: Konfiguration der Option
// return Funktion fuer die Option
function initOptAction(optAction, item = undefined, optSet = undefined, optConfig = undefined) {
    let fun;

    if (optAction !== undefined) {
        const __CONFIG = ((optConfig !== undefined) ? optConfig : getOptConfig(getOptByName(optSet, item)));
        const __RELOAD = getValue(getValue(__CONFIG, { }).ActionReload, true);

        switch (optAction) {
        case __OPTACTION.SET : fun = function() {
                                       return setOptByName(optSet, item, __CONFIG.SetValue, __RELOAD).catch(defaultCatch);
                                   };
                               break;
        case __OPTACTION.NXT : fun = function() {
                                       return promptNextOptByName(optSet, item, __CONFIG.SetValue, __RELOAD,
                                                  __CONFIG.FreeValue, __CONFIG.SelValue, __CONFIG.MinChoice).catch(defaultCatch);
                                   };
                               break;
        case __OPTACTION.RST : fun = function() {
                                       return resetOptions(optSet, __RELOAD).then(
                                               result => __LOG[3]("RESETTING (" + result + ")..."),
                                               defaultCatch);
                                   };
                               break;
        default :              break;
        }
    }

    return fun;
}

// Gibt fuer einen 'Shared'-Eintrag eine ObjRef zurueck
// shared: Object mit den Angaben 'namespace', 'module' und ggfs. 'item'
// item: Key der Option
// return ObjRef, die das Ziel definiert
function getSharedRef(shared, item = undefined) {
    if (shared === undefined) {
        return undefined;
    }

    const __OBJREF = new ObjRef(__DBDATA);  // Gemeinsame Daten
    const __PROPS = [ 'namespace', 'module', 'item' ];
    const __DEFAULTS = [ __DBMOD.namespace, __DBMOD.name, item ];

    for (let stage in __PROPS) {
        const __DEFAULT = __DEFAULTS[stage];
        const __PROP = __PROPS[stage];
        const __NAME = shared[__PROP];

        if (__NAME === '$') {
            break;
        }

        __OBJREF.chDir(getValue(__NAME, __DEFAULT));
    }

    return __OBJREF;
}

// Gibt diese Config oder, falls 'Shared', ein Referenz-Objekt mit gemeinsamen Daten zurueck
// optConfig: Konfiguration der Option
// item: Key der Option
// return Entweder optConfig oder gemergete Daten auf Basis des in 'Shared' angegebenen Objekts
function getSharedConfig(optConfig, item = undefined) {
    let config = getValue(optConfig, { });
    const __SHARED = config.Shared;

    if (__SHARED !== undefined) {
        const __OBJREF = getSharedRef(__SHARED, item);  // Gemeinsame Daten

        if (getValue(__SHARED.item, '$') !== '$') {  // __OBJREF ist ein Item
            const __REF = valueOf(__OBJREF);

            config = { };  // Neu aufbauen...
            addProps(config, getOptConfig(__REF));
            addProps(config, optConfig);
            config.setConst('SharedData', getOptValue(__REF), false);   // Wert muss schon da sein, NICHT nachladen, sonst ggfs. Promise
        } else {  // __OBJREF enthaelt die Daten selbst
            if (! config.Name) {
                config.Name = __OBJREF.getPath();
            }
            config.setConst('SharedData', __OBJREF);  // Achtung: Ggfs. zirkulaer!
        }
    }

    return config;
}

// Initialisiert die gesetzten Optionen
// optConfig: Konfiguration der Optionen
// optSet: Platz fuer die gesetzten Optionen
// preInit: Vorinitialisierung einzelner Optionen mit 'PreInit'-Attribut
// return Gefuelltes Objekt mit den gesetzten Optionen
function initOptions(optConfig, optSet = undefined, preInit = undefined) {
    let value;

    if (optSet === undefined) {
        optSet = { };
    }

    for (let opt in optConfig) {
        const __OPTCONFIG = optConfig[opt];
        const __PREINIT = getValue(__OPTCONFIG.PreInit, false, true);
        const __ISSHARED = getValue(__OPTCONFIG.Shared, false, true);

        if ((preInit === undefined) || (__PREINIT === preInit)) {
            const __CONFIG = getSharedConfig(__OPTCONFIG, opt);
            const __ALTACTION = getValue(__CONFIG.AltAction, __CONFIG.Action);
            // Gab es vorher einen Aufruf, der einen Stub-Eintrag erzeugt hat, und wurden Daten geladen?
            const __LOADED = ((preInit === false) && optSet[opt].Loaded);
            const __PROMISE = ((__LOADED || ! optSet[opt]) ? undefined : optSet[opt].Promise);
            const __VALUE = (__LOADED ? optSet[opt].Value : undefined);

            optSet[opt] = {
                'Item'      : opt,
                'Config'    : __CONFIG,
                'Loaded'    : (__ISSHARED || __LOADED),
                'Promise'   : __PROMISE,
                'Value'     : initOptValue(__CONFIG, __VALUE),
                'SetValue'  : __CONFIG.SetValue,
                'ReadOnly'  : (__ISSHARED || __CONFIG.ReadOnly),
                'Action'    : initOptAction(__CONFIG.Action, opt, optSet, __CONFIG),
                'AltAction' : initOptAction(__ALTACTION, opt, optSet, __CONFIG)
            };
        } else if (preInit) {  // erstmal nur Stub
            optSet[opt] = {
                'Item'      : opt,
                'Config'    : __OPTCONFIG,
                'Loaded'    : false,
                'Promise'   : undefined,
                'Value'     : initOptValue(__OPTCONFIG),
                'ReadOnly'  : (__ISSHARED || __OPTCONFIG.ReadOnly)
            };
        }
    }

    return optSet;
}

    // Abhaengigkeiten:
    // ================
    // initOptions (PreInit):
    // restoreMemoryByOpt: PreInit oldStorage
    // getStoredCmds: restoreMemoryByOpt
    // runStoredCmds (beforeLoad): getStoredCmds
    // loadOptions (PreInit): PreInit
    // startMemoryByOpt: storage oldStorage
    // initScriptDB: startMemoryByOpt
    // initOptions (Rest): PreInit
    // getMyTeam callback (getOptPrefix): initTeam
    // __MYTEAM (initTeam): initOptions
    // renameOptions: getOptPrefix
    // runStoredCmds (afterLoad): getStoredCmds, renameOptions
    // loadOptions (Rest): PreInit/Rest runStoredCmds
    // updateScriptDB: startMemoryByOpt
    // showOptions: startMemoryByOpt renameOptions
    // buildMenu: showOptions
    // buildForm: showOptions

// Initialisiert die gesetzten Optionen und den Speicher und laedt die Optionen zum Start
// optConfig: Konfiguration der Optionen
// optSet: Platz fuer die gesetzten Optionen
// return Promise auf gefuelltes Objekt mit den gesetzten Optionen
async function startOptions(optConfig, optSet = undefined, classification = undefined) {
    optSet = initOptions(optConfig, optSet, true);  // PreInit

    // Memory Storage fuer vorherige Speicherung...
    myOptMemSize = getMemSize(myOptMem = await restoreMemoryByOpt(optSet.oldStorage));

    // Zwischengespeicherte Befehle auslesen...
    const __STOREDCMDS = getStoredCmds(myOptMem);

    // ... ermittelte Befehle ausfuehren...
    const __LOADEDCMDS = await runStoredCmds(__STOREDCMDS, optSet, true);  // BeforeLoad

    // Bisher noch nicht geladenene Optionen laden...
    await loadOptions(optSet);

    // Memory Storage fuer naechste Speicherung...
    myOptMemSize = getMemSize(myOptMem = startMemoryByOpt(optSet.storage, optSet.oldStorage));

    // Globale Daten ermitteln...
    initScriptDB(optSet);

    optSet = initOptions(optConfig, optSet, false);  // Rest

    if (classification !== undefined) {
        // Umbenennungen durchfuehren...
        await classification.renameOptions();
    }

    // ... ermittelte Befehle ausfuehren...
    await runStoredCmds(__LOADEDCMDS, optSet, false);  // Rest

    // Als globale Daten speichern...
    updateScriptDB(optSet);

    return optSet;
}

// Installiert die Visualisierung und Steuerung der Optionen
// optSet: Platz fuer die gesetzten Optionen
// optParams: Eventuell notwendige Parameter zur Initialisierung
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return Liefert die gesetzten Optionen zurueck
function showOptions(optSet = undefined, optParams = { 'hideMenu' : false }) {
    if (! optParams.hideMenu) {
        buildMenu(optSet).then(() => __LOG[3]("Menu OK"));
    }

    if ((optParams.menuAnchor !== undefined) && (myOptMem !== __OPTMEMINAKTIVE)) {
        buildForm(optParams.menuAnchor, optSet, optParams);
    }

    return optSet;
}

// Setzt eine Option auf einen vorgegebenen Wert
// Fuer kontrollierte Auswahl des Values siehe setNextOpt()
// opt: Config und vorheriger Value der Option
// value: (Bei allen Typen) Zu setzender Wert
// reload: Seite mit neuem Wert neu laden
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function setOpt(opt, value, reload = false, onFulfilled = undefined, onRejected = undefined) {
    return setOptValue(opt, setStored(getOptName(opt), value, reload, getOptConfig(opt).Serial, onFulfilled, onRejected));
}

// Ermittelt die naechste moegliche Option
// opt: Config und Value der Option
// value: Ggfs. zu setzender Wert
// return Zu setzender Wert
function getNextOpt(opt, value = undefined) {
    const __CONFIG = getOptConfig(opt);
    const __VALUE = getOptValue(opt, value);

    switch (__CONFIG.Type) {
    case __OPTTYPES.MC : return getValue(value, getNextValue(__CONFIG.Choice, __VALUE));
    case __OPTTYPES.SW : return getValue(value, ! __VALUE);
    case __OPTTYPES.TF : return getValue(value, ! __VALUE);
    case __OPTTYPES.SD : return getValue(value, __VALUE);
    case __OPTTYPES.SI : break;
    default :            break;
    }

    return __VALUE;
}

// Setzt die naechste moegliche Option
// opt: Config und Value der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function setNextOpt(opt, value = undefined, reload = false, onFulfilled = undefined, onRejected = undefined) {
    return setOpt(opt, getNextOpt(opt, value), reload, onFulfilled, onRejected);
}

// Setzt die naechste moegliche Option oder fragt ab einer gewissen Anzahl interaktiv ab
// opt: Config und Value der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// freeValue: Angabe, ob Freitext zugelassen ist (Default: false)
// minChoice: Ab wievielen Auswahlmoeglichkeiten soll abgefragt werden? (Default: 3)
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function promptNextOpt(opt, value = undefined, reload = false, freeValue = false, selValue = true, minChoice = 3, onFulfilled = undefined, onRejected = undefined) {
    const __CONFIG = getOptConfig(opt);
    const __CHOICE = __CONFIG.Choice;

    if (value || (! __CHOICE) || (__CHOICE.length < minChoice)) {
        return setNextOpt(opt, value, reload, onFulfilled, onRejected);
    }

    const __VALUE = getOptValue(opt, value);

    try {
        const __NEXTVAL = getNextValue(__CHOICE, __VALUE);
        let message = "";

        if (selValue) {
            for (let index = 0; index < __CHOICE.length; index++) {
                message += (index + 1) + ") " + __CHOICE[index] + '\n';
            }
            message += "\nNummer oder Wert eingeben:";
        } else {
            message = __CHOICE.join(" / ") + "\n\nWert eingeben:";
        }

        const __ANSWER = prompt(message, __NEXTVAL);

        if (__ANSWER) {
            const __INDEX = parseInt(__ANSWER, 10) - 1;
            let nextVal = (selValue ? __CHOICE[__INDEX] : undefined);

            if (nextVal === undefined) {
                const __VALTYPE = getValue(__CONFIG.ValType, 'String');
                const __CASTVAL = this[__VALTYPE](__ANSWER);

                if (freeValue || (~ __CHOICE.indexOf(__CASTVAL))) {
                    nextVal = __CASTVAL;
                }
            }

            if (nextVal !== __VALUE) {
                if (nextVal) {
                    return setOpt(opt, nextVal, reload, onFulfilled, onRejected);
                }

                const __LABEL = substParam(__CONFIG.Label, __VALUE);

                showAlert(__LABEL, "Ung\xFCltige Eingabe: " + __ANSWER);
            }
        }
    } catch (ex) {
        __LOG[0]("promptNextOpt: " + ex.message);
    }

    return __VALUE;
}

// Setzt eine Option auf einen vorgegebenen Wert (Version mit Key)
// Fuer kontrollierte Auswahl des Values siehe setNextOptByName()
// optSet: Platz fuer die gesetzten Optionen (und Config)
// item: Key der Option
// value: (Bei allen Typen) Zu setzender Wert
// reload: Seite mit neuem Wert neu laden
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function setOptByName(optSet, item, value, reload = false, onFulfilled = undefined, onRejected = undefined) {
    const __OPT = getOptByName(optSet, item);

    return setOpt(__OPT, value, reload, onFulfilled, onRejected);
}

// Ermittelt die naechste moegliche Option (Version mit Key)
// optSet: Platz fuer die gesetzten Optionen (und Config)
// item: Key der Option
// value: Default fuer ggfs. zu setzenden Wert
// return Zu setzender Wert
function getNextOptByName(optSet, item, value = undefined) {
    const __OPT = getOptByName(optSet, item);

    return getNextOpt(__OPT, value);
}

// Setzt die naechste moegliche Option (Version mit Key)
// optSet: Platz fuer die gesetzten Optionen (und Config)
// item: Key der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function setNextOptByName(optSet, item, value = undefined, reload = false, onFulfilled = undefined, onRejected = undefined) {
    const __OPT = getOptByName(optSet, item);

    return setNextOpt(__OPT, value, reload, onFulfilled, onRejected);
}

// Setzt die naechste moegliche Option oder fragt ab einer gewissen Anzahl interaktiv ab (Version mit Key)
// optSet: Platz fuer die gesetzten Optionen (und Config)
// item: Key der Option
// value: Default fuer ggfs. zu setzenden Wert
// reload: Seite mit neuem Wert neu laden
// freeValue: Angabe, ob Freitext zugelassen ist (Default: false)
// minChoice: Ab wievielen Auswahlmoeglichkeiten soll abgefragt werden? (Default: 3)
// onFulfilled: Reaktion auf Speicherung im resolve-Fall (1. Promise.then()-Parameter)
// onRejected: Reaktion auf Speicherung im reject-Fall (2. Promise.then()-Parameter)
// return Gesetzter Wert
function promptNextOptByName(optSet, item, value = undefined, reload = false, freeValue = false, selValue = true, minChoice = 3, onFulfilled = undefined, onRejected = undefined) {
    const __OPT = getOptByName(optSet, item);

    return promptNextOpt(__OPT, value, reload, freeValue, selValue, minChoice, onFulfilled, onRejected);
}

// Baut das Benutzermenu auf (asynchron im Hintergrund)
// optSet: Gesetzte Optionen
// return Promise auf void
async function buildMenu(optSet) {
    __LOG[3]("buildMenu()");

    for (let opt in optSet) {
        await registerOption(optSet[opt]).then(
                result => __LOG[6](`REGISTEROPTION[${opt}] = ${result}`),
                defaultCatch);
    }
}

// Invalidiert eine (ueber Menu) gesetzte Option
// opt: Zu invalidierende Option
// force: Invalidiert auch Optionen mit 'AutoReset'-Attribut
// return Promise auf resultierenden Wert
function invalidateOpt(opt, force = false) {
    return Promise.resolve(opt.Promise).then(value => {
            if (opt.Loaded && ! opt.ReadOnly) {
                const __CONFIG = getOptConfig(opt);

                // Wert "ungeladen"...
                opt.Loaded = (force || ! __CONFIG.AutoReset);

                if (opt.Loaded && __CONFIG.AutoReset) {
                    // Nur zuruecksetzen, gilt als geladen...
                    setOptValue(opt, initOptValue(__CONFIG));
                }
            }

            return getOptValue(opt);
        }, defaultCatch);
}

// Invalidiert die (ueber Menu) gesetzten Optionen
// optSet: Object mit den Optionen
// force: Invalidiert auch Optionen mit 'AutoReset'-Attribut
// return Promise auf Object mit den geladenen Optionen
async function invalidateOpts(optSet, force = false) {
    for (let opt in optSet) {
        const __OPT = optSet[opt];

        await invalidateOpt(__OPT, force);
    }

    return optSet;
}

// Laedt eine (ueber Menu) gesetzte Option
// opt: Zu ladende Option
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Promise auf gesetzten Wert der gelandenen Option
function loadOption(opt, force = false) {
    if (! opt.Promise) {
        const __CONFIG = getOptConfig(opt);
        const __ISSHARED = getValue(__CONFIG.Shared, false, true);
        const __NAME = getOptName(opt);
        const __DEFAULT = getOptValue(opt, undefined, false, false, false);
        let value;

        if (opt.Loaded && ! __ISSHARED) {
            const __ERROR = "Error: Oprion '" + __NAME + "' bereits geladen!";

            __LOG[0](__MESSAGE);

            return Promise.reject(__MESSAGE);
        }

        if (opt.ReadOnly || __ISSHARED) {
            value = __DEFAULT;
        } else if (! force && __CONFIG.AutoReset) {
            value = initOptValue(__CONFIG);
        } else {
            value = (__CONFIG.Serial ?
                            deserialize(__NAME, __DEFAULT) :
                            GM.getValue(__NAME, __DEFAULT));
        }

        opt.Promise = Promise.resolve(value).then(value => {
                // Paranoide Sicherheitsabfrage (das sollte nie passieren!)...
                if (opt.Loaded || ! opt.Promise) {
                    showAlert("Error", "Unerwarteter Widerspruch zwischen opt.Loaded und opt.Promise", safeStringify(opt));
                }
                __LOG[5]("LOAD " + __NAME + ": " + __LOG.changed(__DEFAULT, value));

                // Wert intern setzen...
                const __VAL = setOptValue(opt, value);

                // Wert als geladen markieren...
                opt.Promise = undefined;
                opt.Loaded = true;

                return __VAL;
            }, defaultCatch);
    }

    return opt.Promise;
}

// Laedt die (ueber Menu) gesetzten Optionen
// optSet: Object mit den Optionen
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Array mit Promises neuer Ladevorgaenge (fuer Objekte mit 'name' und 'value')
function loadOptions(optSet, force = false) {
    const __PROMISES = [];

    for (let opt in optSet) {
        const __OPT = optSet[opt];

        if (! __OPT.Loaded) {
            const __PROMISE = loadOption(__OPT, force).then(value => {
                    __LOG[5]("LOADED " + opt + " << " + value);

                    return Promise.resolve({
                            'name'  : opt,
                            'value' : value
                        });
                }, defaultCatch);

            __PROMISES.push(__PROMISE);
        }
    }

    return Promise.all(__PROMISES);
}

// Entfernt eine (ueber Menu) gesetzte Option (falls nicht 'Permanent')
// opt: Gesetzte Option
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
// reset: Setzt bei Erfolg auf Initialwert der Option (auch fuer nicht 'AutoReset')
// return Promise von GM.deleteValue() (oder void)
function deleteOption(opt, force = false, reset = true) {
    const __CONFIG = getOptConfig(opt);

    if (force || ! __CONFIG.Permanent) {
        const __NAME = getOptName(opt);

        __LOG[4]("DELETE " + __NAME);

        return GM.deleteValue(__NAME).then(voidValue => {
                if (reset || __CONFIG.AutoReset) {
                    setOptValue(opt, initOptValue(__CONFIG));
                }
            }, defaultCatch);
    }

    return Promise.resolve();
}

// Entfernt die (ueber Menu) gesetzten Optionen (falls nicht 'Permanent')
// optSet: Gesetzte Optionen
// optSelect: Liste von ausgewaehlten Optionen, true = entfernen, false = nicht entfernen
// force: Entfernt auch Optionen mit 'Permanent'-Attribut
// reset: Setzt bei Erfolg auf Initialwert der Option
// return Promise auf diesen Vorgang
async function deleteOptions(optSet, optSelect = undefined, force = false, reset = true) {
    const __DELETEALL = ((optSelect === undefined) || (optSelect === true));
    const __OPTSELECT = getValue(optSelect, { });

    for (let opt in optSet) {
        if (getValue(__OPTSELECT[opt], __DELETEALL)) {
            await deleteOption(optSet[opt], force, reset);
        }
    }

    return Promise.resolve();
}

// Benennt eine Option um und laedt sie ggfs. nach
// opt: Gesetzte Option
// name: Neu zu setzender Name (Speicheradresse)
// reload: Wert nachladen statt beizubehalten
// force: Laedt auch Optionen mit 'AutoReset'-Attribut
// return Promise auf umbenannte Option
async function renameOption(opt, name, reload = false, force = false) {
    const __NAME = getOptName(opt);

    if (__NAME !== name) {
        await deleteOption(opt, true, ! reload);

        setOptName(opt, name);

        await invalidateOpt(opt, opt.Loaded);

        if (reload) {
            opt.Loaded = false;

            await loadOption(opt, force);
        }
    }

    return Promise.resolve(opt);
}

// Ermittelt einen neuen Namen mit einem Prefix. Parameter fuer renameOptions()
// name: Gesetzter Name (Speicheradresse)
// prefix: Prefix, das vorangestellt werden soll
// return Neu zu setzender Name (Speicheradresse)
function prefixName(name, prefix) {
    return (prefix + name);
}

// Ermittelt einen neuen Namen mit einem Postfix. Parameter fuer renameOptions()
// name: Gesetzter Name (Speicheradresse)
// postfix: Postfix, das angehaengt werden soll
// return Neu zu setzender Name (Speicheradresse)
function postfixName(name, postfix) {
    return (name + postfix);
}

// Benennt selektierte Optionen nach einem Schema um und laedt sie ggfs. nach
// optSet: Gesetzte Optionen
// optSelect: Liste von ausgewaehlten Optionen, true = nachladen, false = nicht nachladen
// 'reload': Option nachladen?
// 'force': Option auch mit 'AutoReset'-Attribut nachladen?
// renameParam: Wird an renameFun uebergeen
// renameFun: function(name, param) zur Ermittlung des neuen Namens
// - name: Neu zu setzender Name (Speicheradresse)
// - param: Parameter "renameParam" von oben, z.B. Prefix oder Postfix
// return Promise auf diesen Vorgang
async function renameOptions(optSet, optSelect, renameParam = undefined, renameFun = prefixName) {
    if (renameFun === undefined) {
        __LOG[0]("RENAME: Illegale Funktion!");
    }
    for (let opt in optSelect) {
        const __OPTPARAMS = optSelect[opt];
        const __OPT = optSet[opt];

        if (__OPT === undefined) {
            __LOG[0]("RENAME: Option '" + opt + "' nicht gefunden!");
        } else {
            const __NAME = getOptName(__OPT);
            const __NEWNAME = renameFun(__NAME, renameParam);
            const __ISSCALAR = ((typeof __OPTPARAMS) === 'boolean');
            // Laedt die unter dem neuen Namen gespeicherten Daten nach?
            const __RELOAD = (__ISSCALAR ? __OPTPARAMS : __OPTPARAMS.reload);
            // Laedt auch Optionen mit 'AutoReset'-Attribut?
            const __FORCE = (__ISSCALAR ? true : __OPTPARAMS.force);

            await renameOption(__OPT, __NEWNAME, __RELOAD, __FORCE);
        }
    }
}

// Setzt die Optionen in optSet auf die "Werkseinstellungen" des Skripts
// optSet: Gesetzte Optionen
// reload: Seite mit "Werkseinstellungen" neu laden
// return Promise auf diesen Vorgang
async function resetOptions(optSet, reload = true) {
    // Alle (nicht 'Permanent') gesetzten Optionen entfernen...
    await deleteOptions(optSet, true, false, ! reload);

    // ... und ggfs. Seite neu laden (mit "Werkseinstellungen")...
    refreshPage(reload);
}

// ==================== Abschnitt fuer diverse Utilities ====================

// Legt Input-Felder in einem Form-Konstrukt an, falls noetig
// form: <form>...</form>
// props: Map von name:value-Paaren
// type: Typ der Input-Felder (Default: unsichtbare Daten)
// return Ergaenztes Form-Konstrukt
function addInputField(form, props, type = 'hidden') {
    for (let fieldName in props) {
        let field = form[fieldName];
        if (! field) {
            field = document.createElement('input');
            field.type = type;
            field.name = fieldName;
            form.appendChild(field);
        }
        field.value = props[fieldName];
    }

    return form;
}

// Legt unsichtbare Input-Daten in einem Form-Konstrukt an, falls noetig
// form: <form>...</form>
// props: Map von name:value-Paaren
// return Ergaenztes Form-Konstrukt
function addHiddenField(form, props) {
    return addInputField(form, props, 'hidden');
}

// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function addEvent(obj, type, callback, capture = false) {
    if (obj.addEventListener) {
        return obj.addEventListener(type, callback, capture);
    } else if (obj.attachEvent) {
        return obj.attachEvent('on' + type, callback);
    } else {
        __LOG[0]("Could not add " + type + " event:");
        __LOG[2](callback);

        return false;
    }
}

// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
// obj: Betroffenes Objekt, z.B. ein Eingabeelement
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function removeEvent(obj, type, callback, capture = false) {
    if (obj.removeEventListener) {
        return obj.removeEventListener(type, callback, capture);
    } else if (obj.detachEvent) {
        return obj.detachEvent('on' + type, callback);
    } else {
        __LOG[0]("Could not remove " + type + " event:");
        __LOG[2](callback);

        return false;
    }
}

// Hilfsfunktion fuer alle Browser: Fuegt fuer ein Event eine Reaktion ein
// id: ID des betroffenen Eingabeelements
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function addDocEvent(id, type, callback, capture = false) {
    const __OBJ = document.getElementById(id);

    return addEvent(__OBJ, type, callback, capture);
}

// Hilfsfunktion fuer alle Browser: Entfernt eine Reaktion fuer ein Event
// id: ID des betroffenen Eingabeelements
// type: Name des Events, z.B. "click"
// callback: Funktion als Reaktion
// capture: Event fuer Parent zuerst (true) oder Child (false als Default)
// return false bei Misserfolg
function removeDocEvent(id, type, callback, capture = false) {
    const __OBJ = document.getElementById(id);

    return removeEvent(__OBJ, type, callback, capture);
}

// Hilfsfunktion fuer die Ermittlung eines Elements der Seite
// name: Name des Elements (siehe "name=")
// index: Laufende Nummer des Elements (0-based), Default: 0
// doc: Dokument (document)
// return Gesuchtes Element mit der lfd. Nummer index oder undefined (falls nicht gefunden)
function getElement(name, index = 0, doc = document) {
    const __TAGS = doc.getElementsByName(name);
    const __TABLE = (__TAGS ? __TAGS[index] : undefined);

    return __TABLE;
}

// Hilfsfunktion fuer die Ermittlung eines Elements der Seite (Default: Tabelle)
// index: Laufende Nummer des Elements (0-based)
// tag: Tag des Elements ("table")
// doc: Dokument (document)
// return Gesuchtes Element oder undefined (falls nicht gefunden)
function getTable(index, tag = 'table', doc = document) {
    const __TAGS = doc.getElementsByTagName(tag);
    const __TABLE = (__TAGS ? __TAGS[index] : undefined);

    return __TABLE;
}

// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
// name: Name des Tabellen-Elements (siehe "name=")
// index: Laufende Nummer des Tabellen-Elements (0-based), Default: 0
// doc: Dokument (document)
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
function getElementRows(name, index = 0, doc = document) {
    const __TABLE = getElement(name, index, doc);
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);

    return __ROWS;
}

// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
// index: Laufende Nummer des Elements (0-based)
// doc: Dokument (document)
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
function getRows(index, doc = document) {
    const __TABLE = getTable(index, 'table', doc);
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);

    return __ROWS;
}

// Hilfsfunktion fuer die Ermittlung der Zeilen einer Tabelle
// id: ID des Tabellen-Elements
// doc: Dokument (document)
// return Gesuchte Zeilen oder undefined (falls nicht gefunden)
function getRowsById(id, doc = document) {
    const __TABLE = doc.getElementById(id);
    const __ROWS = (__TABLE ? __TABLE.rows : undefined);

    return __ROWS;
}

// ==================== Abschnitt fuer Optionen auf der Seite ====================

// Liefert den Funktionsaufruf zur Option als String
// opt: Auszufuehrende Option
// isAlt: Angabe, ob AltAction statt Action gemeint ist
// value: Ggfs. zu setzender Wert
// serial: Serialization fuer String-Werte (Select, Textarea)
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return String mit dem (reinen) Funktionsaufruf
function getFormAction(opt, isAlt = false, value = undefined, serial = undefined, memory = undefined) {
    const __STORAGE = getMemory(memory);
    const __MEMORY = __STORAGE.Value;
    const __MEMSTR = __STORAGE.Display;
    const __RUNPREFIX = __STORAGE.Prefix;

    if (__MEMORY !== undefined) {
        const __RELOAD = "window.location.reload()";
        const __SETITEM = function(item, val, quotes = true) {
                              return (__MEMSTR + ".setItem('" + __RUNPREFIX + item + "', " + (quotes ? "'" + val + "'" : val) + "),");
                          };
        const __SETITEMS = function(cmd, key = undefined, val = undefined) {
                              return ('(' + __SETITEM('cmd', cmd) + ((key === undefined) ? "" :
                                      __SETITEM('key', key) + __SETITEM('val', val, false)) + __RELOAD + ')');
                          };
        const __CONFIG = getOptConfig(opt);
        const __SERIAL = getValue(serial, getValue(__CONFIG.Serial, false));
        const __THISVAL = ((__CONFIG.ValType === 'String') ? "'\\x22' + this.value + '\\x22'" : "this.value");
        const __TVALUE = getValue(__CONFIG.ValType, __THISVAL, "new " + __CONFIG.ValType + '(' + __THISVAL + ')');
        const __VALSTR = ((value !== undefined) ? safeStringify(value) : __SERIAL ? "JSON.stringify(" + __TVALUE + ')' : __TVALUE);
        const __ACTION = (isAlt ? getValue(__CONFIG.AltAction, __CONFIG.Action) : __CONFIG.Action);

        if (__ACTION !== undefined) {
            switch (__ACTION) {
            case __OPTACTION.SET : //return "doActionSet('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                   return __SETITEMS('SET', getOptName(opt), __VALSTR);
            case __OPTACTION.NXT : //return "doActionNxt('" + getOptName(opt) + "', " + getNextOpt(opt, __VALSTR) + ')';
                                   return __SETITEMS('NXT', getOptName(opt), __VALSTR);
            case __OPTACTION.RST : //return "doActionRst()";
                                   return __SETITEMS('RST');
            default :              break;
            }
        }
    }

    return undefined;
}

// Liefert die Funktionsaufruf zur Option als String
// opt: Auszufuehrende Option
// isAlt: Angabe, ob AltAction statt Action gemeint ist
// value: Ggfs. zu setzender Wert
// type: Event-Typ fuer <input>, z.B. "click" fuer "onclick="
// serial: Serialization fuer String-Werte (Select, Textarea)
// memory: __OPTMEM.normal = unbegrenzt gespeichert (localStorage), __OPTMEM.begrenzt = bis Browserende gespeichert (sessionStorage), __OPTMEM.inaktiv
// return String mit dem (reinen) Funktionsaufruf
function getFormActionEvent(opt, isAlt = false, value = undefined, type = 'click', serial = undefined, memory = undefined) {
    const __ACTION = getFormAction(opt, isAlt, value, serial, memory);

    return getValue(__ACTION, "", ' on' + type + '="' + __ACTION + '"');
}

// Hilfsfunktion: Wendet eine Konvertierung auf jede "Zeile" innerhalb eines Textes an
// text: Urspruenglicher Text
// convFun: function(line, index, arr): Konvertiert line in "Zeile" line des Arrays arr
// separator: Zeilentrenner im Text (Default: '\n')
// thisArg: optionaler this-Parameter fuer die Konvertierung
// limit: optionale Begrenzung der Zeilen
// return String mit dem neuen Text
function eachLine(text, convFun, separator = '\n', thisArg = undefined, limit = undefined) {
    const __ARR = text.split(separator, limit);
    const __RES = __ARR.map(convFun, thisArg);

    return __RES.join(separator);
}

// Hilfsfunktion: Ergaenzt einen HTML-Code um einen Titel (ToolTip)
// html: Urspruenglicher HTML-Code (z.B. ein HTML-Element oder Text)
// title: Im ToolTip angezeigter Text
// separator: Zeilentrenner im Text (Default: '|')
// limit: optionale Begrenzung der Zeilen
// return String mit dem neuen HTML-Code
function withTitle(html, title, separator = '|', limit = undefined) {
    if (title && title.length) {
        return eachLine(html, line => '<abbr title="' + title + '">' + line + '</abbr>', separator, undefined, limit);
    } else {
        return html;
    }
}

// Hilfsfunktion: Ermittelt einen Label- oder FormLabel-Eintrag (Default)
// label: Config-Eintrag fuer Label oder FormLabel
// defLabel: Ersatzwert, falls label nicht angegeben
// isSelect: Angabe, ob ein Parameter angezeigt wird (Default: false)
// isForm: Angabe, ob ein FormLabel gesucht ist (Default: true)
// return Vollstaendiger Label- oder FormLabel-Eintrag
function formatLabel(label, defLabel = undefined, isSelect = false, isForm = true) {
    const __LABEL = getValue(label, defLabel);

    if (isSelect && __LABEL && (substParam(__LABEL, '_') === __LABEL)) {
        return __LABEL + (isForm ? "|$" : " $");
    } else {
        return __LABEL;
    }
}

// Zeigt eine Option auf der Seite als Auswahlbox an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionSelect(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt);
    const __ACTION = getFormActionEvent(opt, false, undefined, 'change', undefined);
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label, true);
    const __TITLE = substParam(getValue(__CONFIG.Title, __CONFIG.Label), __VALUE);
    const __LABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
    let element = '<select name="' + __NAME + '" id="' + __NAME + '"' + __ACTION + '>';

    if (__CONFIG.FreeValue && ! (~ __CONFIG.Choice.indexOf(__VALUE))) {
        element += '\n<option value="' + __VALUE + '" SELECTED>' + __VALUE + '</option>';
    }
    for (let value of __CONFIG.Choice) {
        element += '\n<option value="' + value + '"' +
                   ((value === __VALUE) ? ' SELECTED' : "") +
                   '>' + value + '</option>';
    }
    element += '\n</select>';

    return withTitle(substParam(__LABEL, element), __TITLE);
}

// Zeigt eine Option auf der Seite als Radiobutton an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionRadio(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, false, true, 'click', false);
    const __ALTACTION = getFormActionEvent(opt, true, false, 'click', false);
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel); // nur nutzen, falls angegeben
    const __TITLE = getValue(__CONFIG.Title, '$');
    const __TITLEON = substParam(__TITLE, __CONFIG.Label);
    const __TITLEOFF = substParam(getValue(__CONFIG.AltTitle, __TITLE), __CONFIG.AltLabel);
    const __ELEMENTON  = '<input type="radio" name="' + __NAME +
                         '" id="' + __NAME + 'ON" value="1"' +
                         (__VALUE ? ' CHECKED' : __ACTION) +
                         ' /><label for="' + __NAME + 'ON">' +
                         __CONFIG.Label + '</label>';
    const __ELEMENTOFF = '<input type="radio" name="' + __NAME +
                         '" id="' + __NAME + 'OFF" value="0"' +
                         (__VALUE ? __ALTACTION : ' CHECKED') +
                         ' /><label for="' + __NAME + 'OFF">' +
                         __CONFIG.AltLabel + '</label>';
    const __ELEMENT = [
                          withTitle(__FORMLABEL, __VALUE ? __TITLEON : __TITLEOFF),
                          withTitle(__ELEMENTON, __TITLEON),
                          withTitle(__ELEMENTOFF, __TITLEOFF)
                      ];

    return ((__FORMLABEL && __FORMLABEL.length) ? __ELEMENT : __ELEMENT.slice(1, 3));
}

// Zeigt eine Option auf der Seite als Checkbox an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionCheckbox(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, 'click', false);
    const __VALUELABEL = (__VALUE ? __CONFIG.Label : getValue(__CONFIG.AltLabel, __CONFIG.Label));
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label);
    const __TITLE = substParam(getValue(__VALUE ? __CONFIG.Title : getValue(__CONFIG.AltTitle, __CONFIG.Title), '$'), __VALUELABEL);

    return withTitle('<input type="checkbox" name="' + __NAME +
                     '" id="' + __NAME + '" value="' + __VALUE + '"' +
                     (__VALUE ? ' CHECKED' : "") + __ACTION + ' /><label for="' +
                     __NAME + '">' + __FORMLABEL + '</label>', __TITLE);
}

// Zeigt eine Option auf der Seite als Daten-Textfeld an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionTextarea(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt);
    const __ACTION = getFormActionEvent(opt, false, undefined, 'submit', undefined);
    const __SUBMIT = getValue(__CONFIG.Submit, "");
    //const __ONSUBMIT = (__SUBMIT.length ? ' onKeyDown="' + __SUBMIT + '"': "");
    const __ONSUBMIT = (__SUBMIT ? ' onKeyDown="' + __SUBMIT + '"': "");
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __CONFIG.Label);
    const __TITLE = substParam(getValue(__CONFIG.Title, '$'), __FORMLABEL);
    const __ELEMENTLABEL = '<label for="' + __NAME + '">' + __FORMLABEL + '</label>';
    const __ELEMENTTEXT = '<textarea name="' + __NAME + '" id="' + __NAME + '" cols="' + __CONFIG.Cols +
                           '" rows="' + __CONFIG.Rows + '"' + __ONSUBMIT + __ACTION + '>' +
                           safeStringify(__VALUE, __CONFIG.Replace, __CONFIG.Space) + '</textarea>';

    return [ withTitle(__ELEMENTLABEL, __TITLE), __ELEMENTTEXT ];
}

// Zeigt eine Option auf der Seite als Button an
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionButton(opt) {
    const __CONFIG = getOptConfig(opt);
    const __NAME = getOptName(opt);
    const __VALUE = getOptValue(opt, false);
    const __ACTION = getFormActionEvent(opt, __VALUE, ! __VALUE, 'click', false);
    const __BUTTONLABEL = (__VALUE ? getValue(__CONFIG.AltLabel, __CONFIG.Label) : __CONFIG.Label);
    const __FORMLABEL = formatLabel(__CONFIG.FormLabel, __BUTTONLABEL);
    const __BUTTONTITLE = substParam(getValue(__VALUE ? getValue(__CONFIG.AltTitle, __CONFIG.Title) : __CONFIG.Title, '$'), __BUTTONLABEL);

    return '<label for="' + __NAME + '">' + __FORMLABEL + '</label>' +
           withTitle('<input type="button" name="" + ' + __NAME +
                     '" id="' + __NAME + '" value="' + __BUTTONLABEL +
                     '"' + __ACTION + '/>', __BUTTONTITLE);
}

// Zeigt eine Option auf der Seite an (je nach Typ)
// opt: Anzuzeigende Option
// return String mit dem HTML-Code
function getOptionElement(opt) {
    const __CONFIG = getOptConfig(opt);
    const __TYPE = getValue(__CONFIG.FormType, __CONFIG.Type);
    let element = "";

    if (! __CONFIG.Hidden) {
        switch (__TYPE) {
        case __OPTTYPES.MC : element = getOptionSelect(opt);
                             break;
        case __OPTTYPES.SW : if (__CONFIG.FormLabel !== undefined) {
                                 element = getOptionCheckbox(opt);
                             } else {
                                 element = getOptionRadio(opt);
                             }
                             break;
        case __OPTTYPES.TF : element = getOptionCheckbox(opt);
                             break;
        case __OPTTYPES.SD : element = getOptionTextarea(opt);
                             break;
        case __OPTTYPES.SI : element = getOptionButton(opt);
                             break;
        default :            break;
        }

        if ((typeof element) !== 'string') {
            element = '<div>' + Array.from(element).join('<br />') + '</div>';
        }
    }

    return element;
}

// Gruppiert die Daten eines Objects nach einem Kriterium
// data: Object mit Daten
// byFun: function(val), die das Kriterium ermittelt. Default: value
// filterFun: function(key, index, arr), die das Kriterium key im Array arr an der Stelle index vergleicht. Default: Wert identisch
// sortFun: function(a, b), nach der die Kriterien sortiert werden. Default: Array.sort()
// return Neues Object mit Eintraegen der Form <Kriterium> : [ <alle Keys zu diesem Kriterium> ]
function groupData(data, byFun, filterFun, sortFun) {
    const __BYFUN = (byFun || (val => val));
    const __FILTERFUN = (filterFun || ((key, index, arr) => (arr[index] === key)));
    const __KEYS = Object.keys(data);
    const __VALS = Object.values(data);
    const __BYKEYS = __VALS.map(__BYFUN);
    const __BYKEYSET = new Set(__BYKEYS);
    const __BYKEYARRAY = [...__BYKEYSET];
    const __SORTEDKEYS = __BYKEYARRAY.sort(sortFun);
    const __GROUPEDKEYS = __SORTEDKEYS.map(byVal => __KEYS.filter((key, index, arr) => __FILTERFUN(byVal, index, __BYKEYS)));
    const __ASSIGN = ((keyArr, valArr) => Object.assign({ }, ...keyArr.map((key, index) => ({ [key] : valArr[index] }))));

    return __ASSIGN(__SORTEDKEYS, __GROUPEDKEYS);
}

// Baut das Benutzermenu auf der Seite auf
// optSet: Gesetzte Optionen
// optParams: Eventuell notwendige Parameter
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return String mit dem HTML-Code
function getForm(optSet, optParams = { }) {
    const __FORM = '<form id="options" method="POST"><table><tbody><tr>';
    const __FORMEND = '</tr></tbody></table></form>';
    const __FORMWIDTH = getValue(optParams.formWidth, 3);
    const __FORMBREAK = getValue(optParams.formBreak, __FORMWIDTH);
    const __SHOWFORM = getOptValue(optSet.showForm, true) ? optParams.showForm : { 'showForm' : true };
    const __PRIOOPTS = groupData(optSet, opt => getOptConfig(opt).FormPrio);
    let form = __FORM;
    let count = 0;   // Bisher angezeigte Optionen
    let column = 0;  // Spalte der letzten Option (1-basierend)

    for (let optKeys of Object.values(__PRIOOPTS)) {
        for (let optKey of optKeys) {
            if (checkItem(optKey, __SHOWFORM, optParams.hideForm)) {
                const __ELEMENT = getOptionElement(optSet[optKey]);
                const __TDOPT = ((~ __ELEMENT.indexOf('|')) ? "" : ' colspan="2"');

                if (__ELEMENT) {
                    if (++count > __FORMBREAK) {
                        if (++column > __FORMWIDTH) {
                            column = 1;
                        }
                    }
                    if (column === 1) {
                        form += '</tr><tr>';
                    }
                    form += '\n<td' + __TDOPT + '>' + __ELEMENT.replace('|', '</td><td>') + '</td>';
                }
            }
        }
    }
    form += '\n' + __FORMEND;

    return form;
}

// Fuegt das Script in die Seite ein
// optSet: Gesetzte Optionen
// optParams: Eventuell notwendige Parameter
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// return String mit dem HTML-Code fuer das Script
function getScript(optSet, optParams = { }) {
    //const __SCRIPT = '<script type="text/javascript">function activateMenu() { console.log("TADAAA!"); }</script>';
    //const __SCRIPT = '<script type="text/javascript">\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionNxt(key, value) { alert("SET " + key + " = " + value); }\n\tfunction doActionRst(key, value) { alert("RESET"); }\n</script>';
    //const __FORM = '<form method="POST"><input type="button" id="showOpts" name="showOpts" value="Optionen anzeigen" onclick="activateMenu()" /></form>';
    const __SCRIPT = "";

    //window.eval('function activateMenu() { console.log("TADAAA!"); }');

    return __SCRIPT;
}

// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
// anchor: Element, das als Anker fuer die Anzeige dient
// optSet: Gesetzte Optionen
// optParams: Eventuell notwendige Parameter
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
function buildForm(anchor, optSet, optParams = { }) {
    __LOG[3]("buildForm()");

    const __FORM = getForm(optSet, optParams);
    const __SCRIPT = getScript(optSet, optParams);

    addForm(anchor, __FORM, __SCRIPT);
}

// Informationen zu hinzugefuegten Forms
const __FORMS = { };

// Zeigt das Optionsmenu auf der Seite an (im Gegensatz zum Benutzermenu)
// anchor: Element, das als Anker fuer die Anzeige dient
// form: HTML-Form des Optionsmenu (hinten angefuegt)
// script: Script mit Reaktionen
function addForm(anchor, form = "", script = "") {
    const __OLDFORM = __FORMS[anchor];
    const __REST = (__OLDFORM === undefined) ? anchor.innerHTML :
                   anchor.innerHTML.substring(0, anchor.innerHTML.length - __OLDFORM.Script.length - __OLDFORM.Form.length);

    __FORMS[anchor] = {
                          'Script' : script,
                          'Form'   : form
                      };

    anchor.innerHTML = __REST + script + form;
}

// ==================== Abschnitt fuer Klasse Classification ====================

// Basisklasse fuer eine Klassifikation der Optionen nach Kriterium (z.B. Erst- und Zweitteam oder Fremdteam)
function Classification() {
    'use strict';

    this.renameFun = prefixName;
    //this.renameParamFun = undefined;
    this.optSet = undefined;
    this.optSelect = { };
}

Class.define(Classification, Object, {
                    'renameOptions' : function() {
                                          const __PARAM = this.renameParamFun();

                                          if (__PARAM !== undefined) {
                                              // Klassifizierte Optionen umbenennen...
                                              return renameOptions(this.optSet, this.optSelect, __PARAM, this.renameFun);
                                          } else {
                                              return Promise.resolve();
                                          }
                                      },
                    'deleteOptions' : function(ignList) {
                                          const __OPTSELECT = addProps([], this.optSelect, null, ignList);

                                          return deleteOptions(this.optSet, __OPTSELECT, true, true);
                                      }
                });

// ==================== Ende Abschnitt fuer Klasse Classification ====================

// ==================== Abschnitt fuer Klasse TeamClassification ====================

// Klasse fuer die Klassifikation der Optionen nach Team (Erst- und Zweitteam oder Fremdteam)
function TeamClassification() {
    'use strict';

    Classification.call(this);

    this.team = undefined;
    this.teamParams = undefined;
}

Class.define(TeamClassification, Classification, {
                    'renameParamFun' : function() {
                                           const __MYTEAM = (this.team = getMyTeam(this.optSet, this.teamParams, this.team));

                                           if (__MYTEAM.LdNr) {
                                               // Prefix fuer die Optionen mit gesonderten Behandlung...
                                               return __MYTEAM.LdNr.toString() + '.' + __MYTEAM.LgNr.toString() + ':';
                                           } else {
                                               return undefined;
                                           }
                                       }
                });

// ==================== Ende Abschnitt fuer Klasse TeamClassification ====================

// ==================== Abschnitt fuer Klasse Team ====================

// Klasse fuer Teamdaten
function Team(team, land, liga) {
    'use strict';

    this.Team = team;
    this.Land = land;
    this.Liga = liga;
    this.LdNr = getLandNr(land);
    this.LgNr = getLigaNr(liga);
}

Class.define(Team, Object, {
                    '__TEAMITEMS' : {   // Items, die in Team als Teamdaten gesetzt werden...
                                        'Team' : true,
                                        'Liga' : true,
                                        'Land' : true,
                                        'LdNr' : true,
                                        'LgNr' : true
                                    }
                });

// ==================== Ende Abschnitt fuer Klasse Team ====================

// ==================== Abschnitt fuer Klasse Verein ====================

// Klasse fuer Vereinsdaten
function Verein(team, land, liga, id, manager, flags) {
    'use strict';

    Team.call(this, team, land, liga);

    this.ID = id;
    this.Manager = manager;
    this.Flags = (flags || []);
}

Class.define(Verein, Team, {
                    '__TEAMITEMS' : {   // Items, die in Verein als Teamdaten gesetzt werden...
                                        'Team'    : true,
                                        'Liga'    : true,
                                        'Land'    : true,
                                        'LdNr'    : true,
                                        'LgNr'    : true,
                                        'ID'      : true,
                                        'Manager' : true,
                                        'Flags'   : true
                                    }
                });

// ==================== Ende Abschnitt fuer Klasse Verein ====================

// ==================== Spezialisierter Abschnitt fuer Optionen ====================

// Gesetzte Optionen (wird von initOptions() angelegt und von loadOptions() gefuellt):
const __OPTSET = { };

// Teamparameter fuer getrennte Speicherung der Optionen fuer Erst- und Zweitteam...
const __TEAMCLASS = new TeamClassification();

// Optionen mit Daten, die ZAT- und Team-bezogen gemerkt werden...
__TEAMCLASS.optSelect = {
                            'datenZat'        : true,
                            'oldDatenZat'     : true,
                            'fingerprints'    : true,
                            'birthdays'       : true,
                            'tClasses'        : true,
                            'progresses'      : true,
                            'ziehAnz'         : true,
                            'ziehAnzAufstieg' : true,
                            'zatAges'         : true,
                            'trainiert'       : true,
                            'positions'       : true,
                            'skills'          : true,
                            'foerderung'      : true
                        };

// Gibt die Teamdaten zurueck und aktualisiert sie ggfs. in der Option
// optSet: Platz fuer die gesetzten Optionen
// teamParams: Dynamisch ermittelte Teamdaten ('Team', 'Liga', 'Land', 'LdNr' und 'LgNr')
// myTeam: Objekt fuer die Teamdaten
// return Die Teamdaten oder undefined bei Fehler
function getMyTeam(optSet = undefined, teamParams = undefined, myTeam = new Team()) {
    if (teamParams !== undefined) {
        addProps(myTeam, teamParams, myTeam.__TEAMITEMS);
        __LOG[2]("Ermittelt: " + safeStringify(myTeam));
        // ... und abspeichern, falls erweunscht...
        if (optSet && optSet.team) {
            setOpt(optSet.team, myTeam, false);
        }
    } else {
        const __TEAM = ((optSet && optSet.team) ? getOptValue(optSet.team) : undefined);  // Gespeicherte Parameter

        if ((__TEAM !== undefined) && (__TEAM.Land !== undefined)) {
            addProps(myTeam, __TEAM, myTeam.__TEAMITEMS);
            __LOG[2]("Gespeichert: " + safeStringify(myTeam));
        } else {
            __LOG[6]("Team nicht ermittelt: " + safeStringify(__TEAM));
        }
    }

    return myTeam;
}

// Behandelt die Optionen und laedt das Benutzermenu
// optConfig: Konfiguration der Optionen
// optSet: Platz fuer die gesetzten Optionen
// optParams: Eventuell notwendige Parameter zur Initialisierung
// 'hideMenu': Optionen werden zwar geladen und genutzt, tauchen aber nicht im Benutzermenu auf
// 'teamParams': Getrennte Daten-Option wird genutzt, hier: Team() mit 'LdNr'/'LgNr' des Erst- bzw. Zweitteams
// 'menuAnchor': Startpunkt fuer das Optionsmenu auf der Seite
// 'showForm': Checkliste der auf der Seite sichtbaren Optionen (true fuer sichtbar)
// 'hideForm': Checkliste der auf der Seite unsichtbaren Optionen (true fuer unsichtbar)
// 'formWidth': Anzahl der Elemente pro Zeile
// 'formBreak': Elementnummer des ersten Zeilenumbruchs
// return Promise auf gefuelltes Objekt mit den gesetzten Optionen
function buildOptions(optConfig, optSet = undefined, optParams = { 'hideMenu' : false }) {
    // Klassifikation ueber Land und Liga des Teams...
    __TEAMCLASS.optSet = optSet;  // Classification mit optSet verknuepfen
    __TEAMCLASS.teamParams = optParams.teamParams;  // Ermittelte Parameter

    return startOptions(optConfig, optSet, __TEAMCLASS).then(optSet => {
                    if (optParams.getDonation) {
                        // Jugendfoerderung aus der Options-HTML-Seite ermitteln...
                        const __BOXDONATION = document.getElementsByTagName('option');
                        const __DONATION = getSelectionFromComboBox(__BOXDONATION, 10000, 'Number');

                        __LOG[3]("Jugendf\xF6rderung: " + __DONATION + " Euro");

                        // ... und abspeichern...
                        setOpt(optSet.foerderung, __DONATION, false);
                    }

                    return showOptions(optSet, optParams);
                }, defaultCatch);
}

// ==================== Ende Abschnitt fuer Optionen ====================

// ==================== Abschnitt genereller Code zur Anzeige der Jugend ====================

// Funktionen ***************************************************************************

// Erschafft die Spieler-Objekte und fuellt sie mit Werten
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// optSet: Gesetzte Optionen (und Config)
// colIdx: Liste von Spaltenindices der gesuchten Werte
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
// offsetLower: Ignorierte Zeilen unterhalb der Daten
// page: 1: Teamuebersicht, 2: Spielereinzelwerte, 3: Opt. Skill, 4: Optionen, Default: 0
function init(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, page = 0) {
    storePlayerDataFromHTML(playerRows, optSet, colIdx, offsetUpper, offsetLower, page);

    const __SAISON = getOptValue(optSet.saison);
    const __AKTZAT = getOptValue(optSet.aktuellerZat);
    const __GEALTERT = ((__AKTZAT >= 72) ? (getIntFromHTML(playerRows[playerRows.length - offsetLower - 1].cells, colIdx.Age) < 13) : false);
    const __CURRZAT = (__GEALTERT ? 0 : __AKTZAT);
    const __LGNR = __TEAMCLASS.team.LgNr;
    const __KLASSE = (__LGNR > 1) ? (__LGNR > 3) ? 3 : 2 : 1;
    const __DONATION = getOptValue(optSet.foerderung);
    const __BIRTHDAYS = getOptValue(optSet.birthdays, []);
    const __TCLASSES = getOptValue(optSet.tClasses, []);
    const __PROGRESSES = getOptValue(optSet.progresses, []);
    const __ZATAGES = getOptValue(optSet.zatAges, []);
    const __TRAINIERT = getOptValue(optSet.trainiert, []);
    const __POSITIONS = getOptValue(optSet.positions, []);
    const __SKILLS = getOptValue(optSet.skills, []);
    const __ISSKILLPAGE = (page === 2);
    const __BASEDATA = [ __BIRTHDAYS, __TCLASSES, __PROGRESSES ];  // fuer initPlayer
    const __DATA = (__ISSKILLPAGE ? [ __SKILLS, __BASEDATA ] : [ __BASEDATA, __SKILLS ]);  // fuer initPlayer: [0] = von HTML-Seite, [1] = aus gespeicherten Daten
    const __IDMAP = getPlayerIdMap(optSet);
    const __CATIDS = __IDMAP.catIds;
    const __PLAYERS = [];

    __LOG[5](__IDMAP);

    for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++) {
        const __CELLS = playerRows[i].cells;

        if (__CELLS.length > 1) {
            const __LAND = getStringFromHTML(__CELLS, colIdx.Land);
            const __AGE = getIntFromHTML(__CELLS, colIdx.Age);
            const __ISGOALIE = isGoalieFromHTML(__CELLS, colIdx.Age);
            const __AKTION = getElementFromHTML(__CELLS, colIdx.Akt);

            const __NEWPLAYER = new PlayerRecord(__LAND, __AGE, __ISGOALIE, __SAISON, __CURRZAT, __DONATION);

            __NEWPLAYER.initPlayer(__DATA[0], j, __ISSKILLPAGE);

            const __IDX = selectPlayerIndex(__NEWPLAYER, j, __CATIDS);

            __NEWPLAYER.initPlayer(__DATA[1], __IDX, ! __ISSKILLPAGE);

            __NEWPLAYER.prognoseSkills();

            if (! __ISSKILLPAGE) {
                __NEWPLAYER.setZusatz(__ZATAGES[__IDX], __TRAINIERT[__IDX], __POSITIONS[__IDX]);
            }

            __NEWPLAYER.createWarnDraw(__AKTION, __KLASSE);

            __PLAYERS[j++] = __NEWPLAYER;
        }
    }

    if (__ISSKILLPAGE) {
        calcPlayerData(__PLAYERS, optSet);
    } else {
        setPlayerData(__PLAYERS, optSet);
    }

    storePlayerIds(__PLAYERS, optSet);

    return __PLAYERS;
}

// Berechnet die Identifikations-IDs (Fingerprints) der Spieler neu und speichert diese
function getPlayerIdMap(optSet) {
    const __FINGERPRINTS = getOptValue(optSet.fingerprints, []);
    const __MAP = {
                      'ids'    : { },
                      'cats'   : [],
                      'catIds' : { }
                  };
    const __IDS = __MAP.ids;
    const __CATS = __MAP.cats;
    const __CATIDS = __MAP.catIds;

    for (let i = 0; i < __FINGERPRINTS.length; i++) {
        const __ID = __FINGERPRINTS[i];
        const __CAT = PlayerRecord.prototype.getCatFromFingerPrint(__ID);

        if (__ID) {
            if (! __CATIDS[__CAT]) {
                __CATIDS[__CAT] = { };
            }
            __IDS[__ID] = i;
            __CATS[i] = __CAT;
            __CATIDS[__CAT][__ID] = i;
        }
    }

    return __MAP;
}

// Berechnet die Identifikations-IDs (Fingerprints) der Spieler neu und speichert diese
// players: Array von PlayerRecord mit den Spielerdaten
// optSet: Gesetzte Optionen (und Config)
function storePlayerIds(players, optSet) {
    const __FINGERPRINTS = [];

    for (let i = 0; i < players.length; i++) {
        const __PLAYER = players[i];

        if ((__PLAYER.zatGeb !== undefined) && (__PLAYER.talent !== undefined) && (__PLAYER.positions !== undefined)) {
            __FINGERPRINTS[i]  = __PLAYER.getFingerPrint();
        }
    }

    setOpt(optSet.fingerprints, __FINGERPRINTS, false);
}

// Sucht fuer den Spieler den Eintrag aus catIds heraus und gibt den (geloeschten) Index zurueck
// player: PlayerRecord mit den Daten eines Spielers
// index: Position des Spielers im neuen Array von Spielerdaten
// catIds: PlayerIdMap zum Finden des Spielers ueber die Spielerdaten
// return Original-Index der Daten dieses Spielers im Array von Spielerdaten
function selectPlayerIndex(player, index, catIds) {
    const __MYCAT = player.getCat();
    const __CATS = catIds[__MYCAT];
    const __ID = player.findInFingerPrints(__CATS);
    let idx = index;

    if (__ID !== undefined) {
        idx = __CATS[__ID];
        delete __CATS[__ID];
    }

    return idx;
}

// Speichtert die abgeleiteten Werte in den Spieler-Objekten
// players: Array von PlayerRecord mit den Spielerdaten
// optSet: Gesetzte Optionen (und Config)
function setPlayerData(players, optSet) {
    const __ZIEHANZAHL = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
    let ziehAnzAufstieg = 0;
    const __ZATAGES = [];
    const __TRAINIERT = [];
    const __POSITIONS = [];

    for (let i = 0; i < players.length; i++) {
        const __ZUSATZ = players[i].calcZusatz();

        if (__ZUSATZ.zatAge !== undefined) {  // braucht Geburtstag fuer gueltige Werte!
            const __INDEX = players[i].calcZiehIndex();  // Lfd. Nummer des Abrechnungsmonats (0-basiert)

            if ((__INDEX >= 0) && (__INDEX < __ZIEHANZAHL.length)) {
                __ZIEHANZAHL[__INDEX]++;
            }

            __ZATAGES[i] = __ZUSATZ.zatAge;
        }
        if (players[i].isZiehAufstieg()) {
            ziehAnzAufstieg++;
        }
        __TRAINIERT[i] = __ZUSATZ.trainiert;
        __POSITIONS[i] = __ZUSATZ.bestPos;
    }

    setOpt(optSet.ziehAnz, __ZIEHANZAHL, false);
    setOpt(optSet.ziehAnzAufstieg, ziehAnzAufstieg, false);
    setOpt(optSet.zatAges, __ZATAGES, false);
    setOpt(optSet.trainiert, __TRAINIERT, false);
    setOpt(optSet.positions, __POSITIONS, false);
}

// Berechnet die abgeleiteten Werte in den Spieler-Objekten neu und speichert diese
// players: Array von PlayerRecord mit den Spielerdaten
// optSet: Gesetzte Optionen (und Config)
function calcPlayerData(players, optSet) {
    const __ZATAGES = [];
    const __TRAINIERT = [];
    const __POSITIONS = [];

    for (let i = 0; i < players.length; i++) {
        const __ZUSATZ = players[i].calcZusatz();

        if (__ZUSATZ.zatAge !== undefined) {  // braucht Geburtstag fuer gueltige Werte!
            __ZATAGES[i] = __ZUSATZ.zatAge;
        }
        __TRAINIERT[i] = __ZUSATZ.trainiert;
        __POSITIONS[i] = __ZUSATZ.bestPos;
    }

    setOpt(optSet.zatAges, __ZATAGES, false);
    setOpt(optSet.trainiert, __TRAINIERT, false);
    setOpt(optSet.positions, __POSITIONS, false);
}

// Ermittelt die fuer diese Seite relevanten Werte in den Spieler-Objekten aus den Daten der Seite und speichert diese
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// optSet: Gesetzte Optionen (und Config)
// colIdx: Liste von Spaltenindices der gesuchten Werte
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
// offsetLower: Ignorierte Zeilen unterhalb der Daten
// page: 1: Teamuebersicht, 2: Spielereinzelwerte, 3: Opt. Skill, 4: Optionen, Default: 0
function storePlayerDataFromHTML(playerRows, optSet, colIdx, offsetUpper = 1, offsetLower = 0, page = 0) {
    const __COLDEFS = [ { }, {
                                'birthdays'  : { 'name' : 'birthdays', 'getFun' : getIntFromHTML, 'params' : [ colIdx.Geb ] },
                                'tClasses'   : { 'name' : 'tClasses', 'getFun' : getTalentFromHTML, 'params' : [ colIdx.Tal ] },
                                'progresses' : { 'name' : 'progresses', 'getFun' : getAufwertFromHTML, 'params' : [ colIdx.Auf, getOptValue(optSet.shortAufw, true) ] }
                            }, {
                                'skills'     : { 'name' : 'skills', 'getFun' : getSkillsFromHTML, 'params' : [ colIdx ]}
                            } ][getValueIn(page, 1, 2, 0)];

    return storePlayerDataColsFromHTML(playerRows, optSet, __COLDEFS, offsetUpper, offsetLower);
}

// Ermittelt bestimmte Werte in den Spieler-Objekten aus den Daten der Seite und speichert diese
// playerRows: Array von Zeilen mit Array cells (Spielertabelle)
// optSet: Gesetzte Optionen (und Config)
// colDefs: Informationen zu ausgewaehlten Datenspalten
// offsetUpper: Ignorierte Zeilen oberhalb der Daten
// offsetLower: Ignorierte Zeilen unterhalb der Daten
function storePlayerDataColsFromHTML(playerRows, optSet, colDefs, offsetUpper = 1, offsetLower = 0) {
    const __DATA = { };

    for (let key in colDefs) {
        __DATA[key] = [];
    }

    for (let i = offsetUpper, j = 0; i < playerRows.length - offsetLower; i++) {
        const __CELLS = playerRows[i].cells;

        if (__CELLS.length > 1) {
            for (let key in colDefs) {
                const __COLDEF = colDefs[key];

                __DATA[key][j] = __COLDEF.getFun(__CELLS, ...__COLDEF.params);
            }
            j++;
        }
    }

    for (let key in colDefs) {
        const __COLDEF = colDefs[key];

        __LOG[7]('Schreibe ' + __COLDEF.name + ': ' + __DATA[key]);

        setOpt(optSet[__COLDEF.name], __DATA[key], false);
    }
}

// Trennt die Gruppen (z.B. Jahrgaenge) mit Linien
function separateGroups(rows, borderString, colIdxSort = 0, offsetUpper = 1, offsetLower = 0, offsetLeft = -1, offsetRight = 0, formatFun = sameValue) {
    if (offsetLeft < 0) {
        offsetLeft = colIdxSort;  // ab Sortierspalte
    }

    for (let i = offsetUpper, newVal, oldVal = formatFun((rows[i].cells[colIdxSort] || { }).textContent); i < rows.length - offsetLower - 1; i++, oldVal = newVal) {
        newVal = formatFun((rows[i + 1].cells[colIdxSort] || { }).textContent);
        if (newVal !== oldVal) {
            for (let j = offsetLeft; j < rows[i].cells.length - offsetRight; j++) {
                rows[i].cells[j].style.borderBottom = borderString;
            }
        }
    }
}

// Klasse ColumnManager *****************************************************************

function ColumnManager(optSet, colIdx, showCol) {
    'use strict';

    __LOG[3]("ColumnManager()");

    const __SHOWCOL = getValue(showCol, true);
    const __SHOWALL = ((__SHOWCOL === true) || (__SHOWCOL.Default === true));

    const __BIRTHDAYS = getOptValue(optSet.birthdays, []).length;
    const __TCLASSES = getOptValue(optSet.tClasses, []).length;
    const __PROGRESSES = getOptValue(optSet.progresses, []).length;

    const __ZATAGES = getOptValue(optSet.zatAges, []).length;
    const __TRAINIERT = getOptValue(optSet.trainiert, []).length;
    const __POSITIONS = getOptValue(optSet.positions, []).length;

    const __EINZELSKILLS = getOptValue(optSet.skills, []).length;
    const __PROJECTION = (__EINZELSKILLS && __ZATAGES);

    this.colIdx = colIdx;

    this.saison = getOptValue(optSet.saison);
    this.gt = getOptValue(optSet.zeigeJahrgang);
    this.gtUxx = getOptValue(optSet.zeigeUxx);

    this.fpId = (__BIRTHDAYS && __TCLASSES && __POSITIONS && getValue(__SHOWCOL.zeigeId, __SHOWALL) && getOptValue(optSet.zeigeId));
    this.warn = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnung, __SHOWALL) && getOptValue(optSet.zeigeWarnung));
    this.warnMonth = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungMonat, __SHOWALL) && getOptValue(optSet.zeigeWarnungMonat));
    this.warnHome = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungHome, __SHOWALL) && getOptValue(optSet.zeigeWarnungHome));
    this.warnDialog = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungDialog, __SHOWALL) && getOptValue(optSet.zeigeWarnungDialog));
    this.warnAufstieg = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungAufstieg, __SHOWALL) && getOptValue(optSet.zeigeWarnungAufstieg));
    this.warnLegende = (__ZATAGES && getValue(__SHOWCOL.zeigeWarnungLegende, __SHOWALL) && getOptValue(optSet.zeigeWarnungLegende));
    this.bar = (__PROJECTION && getValue(__SHOWCOL.zeigeBalken, __SHOWALL) && getOptValue(optSet.zeigeBalken));
    this.barAbs = getOptValue(optSet.absBalken);
    this.donor = getOptValue(optSet.foerderung);
    this.geb = (__BIRTHDAYS && getValue(__SHOWCOL.zeigeGeb, __SHOWALL) && getOptValue(optSet.zeigeGeb));
    this.tal = (__TCLASSES && getValue(__SHOWCOL.zeigeTal, __SHOWALL) && getOptValue(optSet.zeigeTal));
    this.quo = (__ZATAGES && __TRAINIERT && getValue(__SHOWCOL.zeigeQuote, __SHOWALL) && getOptValue(optSet.zeigeQuote));
    this.aufw = (__PROGRESSES && getValue(__SHOWCOL.zeigeAufw, __SHOWALL) && getOptValue(optSet.zeigeAufw));
    this.substAge = (__ZATAGES && getValue(__SHOWCOL.ersetzeAlter, __SHOWALL) && getOptValue(optSet.ersetzeAlter));
    this.alter = (__ZATAGES && getValue(__SHOWCOL.zeigeAlter, __SHOWALL) && getOptValue(optSet.zeigeAlter));
    this.fix = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeFixSkills, __SHOWALL) && getOptValue(optSet.zeigeFixSkills));
    this.tr = (__EINZELSKILLS && __TRAINIERT && getValue(__SHOWCOL.zeigeTrainiert, __SHOWALL) && getOptValue(optSet.zeigeTrainiert));
    this.zat = (__ZATAGES && getValue(__SHOWCOL.zeigeZatDone, __SHOWALL) && getOptValue(optSet.zeigeZatDone));
    this.antHpt = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeAnteilPri, __SHOWALL) && getOptValue(optSet.zeigeAnteilPri));
    this.antNeb = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeAnteilSec, __SHOWALL) && getOptValue(optSet.zeigeAnteilSec));
    this.pri = (__EINZELSKILLS && getValue(__SHOWCOL.zeigePrios, __SHOWALL) && getOptValue(optSet.zeigePrios));
    this.skill = (__EINZELSKILLS && getValue(__SHOWCOL.zeigeSkill, __SHOWALL) && getOptValue(optSet.zeigeSkill));
    this.pos = (__EINZELSKILLS && __POSITIONS && getValue(__SHOWCOL.zeigePosition, __SHOWALL) && getOptValue(optSet.zeigePosition));
    this.anzOpti = ((__EINZELSKILLS && getValue(__SHOWCOL.zeigeOpti, __SHOWALL)) ? getOptValue(optSet.anzahlOpti) : 0);
    this.anzMw =  ((__PROJECTION && getValue(__SHOWCOL.zeigeMW, __SHOWALL)) ? getOptValue(optSet.anzahlMW) : 0);
    this.substSkills = (__PROJECTION && getValue(__SHOWCOL.ersetzeSkills, __SHOWALL) && getOptValue(optSet.ersetzeSkills));
    this.trE = (__PROJECTION && __TRAINIERT && getValue(__SHOWCOL.zeigeTrainiertEnde, __SHOWALL) && getOptValue(optSet.zeigeTrainiertEnde));
    this.zatE = (__ZATAGES && getValue(__SHOWCOL.zeigeZatLeft, __SHOWALL) && getOptValue(optSet.zeigeZatLeft));
    this.antHptE = (__PROJECTION && getValue(__SHOWCOL.zeigeAnteilPriEnde, __SHOWALL) && getOptValue(optSet.zeigeAnteilPriEnde));
    this.antNebE = (__PROJECTION && getValue(__SHOWCOL.zeigeAnteilSecEnde, __SHOWALL) && getOptValue(optSet.zeigeAnteilSecEnde));
    this.priE = (__PROJECTION && getValue(__SHOWCOL.zeigePriosEnde, __SHOWALL) && getOptValue(optSet.zeigePriosEnde));
    this.skillE = (__PROJECTION && getValue(__SHOWCOL.zeigeSkillEnde, __SHOWALL) && getOptValue(optSet.zeigeSkillEnde));
    this.anzOptiE = ((__PROJECTION && getValue(__SHOWCOL.zeigeOptiEnde, __SHOWALL)) ? getOptValue(optSet.anzahlOptiEnde) : 0);
    this.anzMwE = ((__PROJECTION && getValue(__SHOWCOL.zeigeMWEnde, __SHOWALL)) ? getOptValue(optSet.anzahlMWEnde) : 0);
    this.kennzE = getOptValue(optSet.kennzeichenEnde);
}

Class.define(ColumnManager, Object, {
        'toString'       : function() {  // Bisher nur die noetigsten Parameter ausgegeben...
                               let result = "Skillschnitt\t\t" + this.skill + '\n';
                               result += "Beste Position\t" + this.pos + '\n';
                               result += "Optis\t\t\t" + this.anzOpti + '\n';
                               result += "Marktwerte\t\t" + this.anzMw + '\n';
                               result += "Skillschnitt Ende\t" + this.skillE + '\n';
                               result += "Optis Ende\t\t" + this.anzOptiE + '\n';
                               result += "Marktwerte Ende\t" + this.anzMwE + '\n';

                               return result;
                           },
        'addCell'        : function(tableRow) {
                               return tableRow.insertCell(-1);
                           },
        'addAndFillCell' : function(tableRow, value, color, align, digits = 2) {
                               let text = value;

                               if ((value || (value === 0)) && isFinite(value) && (value !== true) && (value !== false)) {
                                   // Zahl einfuegen
                                   if (value < 1000) {
                                       // Mit Nachkommastellen darstellen
                                       text = parseFloat(value).toFixed(digits);
                                   } else {
                                       // Mit Tausenderpunkten darstellen
                                       text = getNumberString(value.toString());
                                   }
                               }

                               // String, Boolean oder Zahl einfuegen...
                               const __CELL = this.addCell(tableRow);

                               __CELL.innerHTML = text;
                               if (color) {
                                   __CELL.style.color = color;
                               }
                               if (align) {
                                   __CELL.align = align;
                               }

                               return __CELL;
                           },
        'addAndBarCell'  : function(tableRow, value, scale = 100, offset = 0, width = 100, height = 10, zoom = 100) {
                               const __VALUE = ((scale && isFinite(value)) ? ((value - offset) / Math.max(1, scale - offset) * 100) : 0);

                               // HTML-Code fuer Anteilsbalken einfuegen...
                               const __CELL = this.addCell(tableRow);

                               __CELL.innerHTML = this.getBarImg(__VALUE, width, height, zoom);
                               __CELL.align = 'left';

                               return __CELL;
                           },
        'getBarImg'      : function(value, width = 100, height = 10, zoom = 100) {
                               const __IMAGE = Math.min(99, Math.max(0, getMulValue(value, 1, 0, 0)));
                               const __LENGTH = getMulValue(width / 100, getMulValue(zoom / 100, value, 0, 0), 0, 0);
                               const __WIDTH = Math.min(width, __LENGTH);
                               const __HEIGHT = Math.max(3, getMulValue(zoom / 100, height * (__LENGTH / __WIDTH), 0, 0));

                               // HTML-Code fuer Anteilsbalken...
                               return '<img src="images/balken/' + __IMAGE + '.GIF" width="' + __WIDTH + '" height=' + __HEIGHT + '>';
                           },
        'addTitles'      : function(headers, titleColor = "#FFFFFF") {
                               // Spaltentitel zentrieren
                               headers.align = "center";

                               // Titel fuer die aktuellen Werte
                               if (this.fpId) {
                                   this.addAndFillCell(headers, "Identifikation", titleColor);
                               }
                               if (this.bar) {
                                   this.addAndFillCell(headers, "Qualit\xE4t", titleColor);
                               }
                               if (this.tal) {
                                   this.addAndFillCell(headers, "Talent", titleColor);
                               }
                               if (this.quo) {
                                   this.addAndFillCell(headers, "Quote", titleColor);
                               }
                               if (this.aufw) {
                                   this.addAndFillCell(headers, "Aufwertung", titleColor);
                               }
                               if (this.geb) {
                                   this.addAndFillCell(headers, "Geb.", titleColor);
                               }
                               if (this.alter && ! this.substAge) {
                                   this.addAndFillCell(headers, "Alter", titleColor);
                               }
                               if (this.fix) {
                                   this.addAndFillCell(headers, "fix", titleColor);
                               }
                               if (this.tr) {
                                   this.addAndFillCell(headers, "tr.", titleColor);
                               }
                               if (this.zat) {
                                   this.addAndFillCell(headers, "ZAT", titleColor);
                               }
                               if (this.antHpt) {
                                   this.addAndFillCell(headers, "%H", titleColor);
                               }
                               if (this.antNeb) {
                                   this.addAndFillCell(headers, "%N", titleColor);
                               }
                               if (this.pri) {
                                   this.addAndFillCell(headers, "Prios", titleColor);
                               }
                               if (this.skill) {
                                   this.addAndFillCell(headers, "Skill", titleColor);
                               }
                               if (this.pos) {
                                   this.addAndFillCell(headers, "Pos", titleColor);
                               }
                               for (let i = 1; i <= 6; i++) {
                                   if (i <= this.anzOpti) {
                                       this.addAndFillCell(headers, "Opti " + i, titleColor);
                                   }
                                   if (i <= this.anzMw) {
                                       this.addAndFillCell(headers, "MW " + i, titleColor);
                                   }
                               }

                               // Titel fuer die Werte mit Ende 18
                               if (this.trE) {
                                   this.addAndFillCell(headers, "tr." + this.kennzE, titleColor);
                               }
                               if (this.zatE) {
                                   this.addAndFillCell(headers, "ZAT" + this.kennzE, titleColor);
                               }
                               if (this.antHptE) {
                                   this.addAndFillCell(headers, "%H" + this.kennzE, titleColor);
                               }
                               if (this.antNebE) {
                                   this.addAndFillCell(headers, "%N" + this.kennzE, titleColor);
                               }
                               if (this.priE) {
                                   this.addAndFillCell(headers, "Prios" + this.kennzE, titleColor);
                               }
                               if (this.skillE) {
                                   this.addAndFillCell(headers, "Skill" + this.kennzE, titleColor);
                               }
                               for (let i = 1; i <= 6; i++) {
                                   if (i <= this.anzOptiE) {
                                       this.addAndFillCell(headers, "Opti " + i + this.kennzE, titleColor);
                                   }
                                   if (i <= this.anzMwE) {
                                       this.addAndFillCell(headers, "MW " + i + this.kennzE, titleColor);
                                   }
                               }
                           },  // Ende addTitles()
        'addValues'      : function(player, playerRow, color = "#FFFFFF") {
                               // Warnlevel des Spielers anpassen...
                               const __WARNDRAW = player.warnDraw || player.warnDrawAufstieg || __NOWARNDRAW;
                               __WARNDRAW.setWarn(this.warn, this.warnMonth, this.warnAufstieg);

                               const __IDXPRI = getIdxPriSkills(player.getPos());
                               const __COLOR = __WARNDRAW.getColor(player.isGoalie ? getColor('TOR') : color); // Angepasst an Ziehwarnung
                               const __POS1COLOR = getColor((player.getPosPercent() > 99.99) ? 'LEI' : player.getPos());
                               const __OSBLAU = getColor("");

                               // Aktuelle Werte
                               if (this.fpId) {
                                   this.addAndFillCell(playerRow, player.getFingerPrint(), __COLOR);
                               }
                               if (this.bar) {
                                   const __VALUE = player.getPrios(player.getPos(), player.__TIME.end);
                                   const __SCALE = (this.barAbs ? 100 : (this.donor / 125));
                                   const __OFFSET = (this.barAbs ? 0 : Math.pow(__SCALE / 20, 2));
                                   const __ZOOM = 50 + __SCALE / 2;

                                   this.addAndBarCell(playerRow, __VALUE, __SCALE, __OFFSET, 100, 10, __ZOOM);
                               }
                               if (this.tal) {
                                   this.addAndFillCell(playerRow, player.getTalent(), __COLOR);
                               }
                               if (this.quo) {
                                   this.addAndFillCell(playerRow, player.getAufwertungsSchnitt(), __COLOR, null, 2);
                               }
                               if (this.colIdx.Auf) {
                                   convertStringFromHTML(playerRow.cells, this.colIdx.Auf, function(aufwert) {
                                                                                               return player.boldPriSkillNames(aufwert);
                                                                                           });
                               }
                               if (this.aufw) {
                                   this.addAndFillCell(playerRow, player.boldPriSkillNames(player.getAufwert()), __COLOR, 'left');
                               }
                               if (this.geb) {
                                   this.addAndFillCell(playerRow, player.getGeb(), __COLOR, null, 0);
                               }
                               if (this.substAge) {
                                   convertStringFromHTML(playerRow.cells, this.colIdx.Age, function(unused) {
                                                                                               return parseFloat(player.getAge()).toFixed(2);
                                                                                           });
                               } else if (this.alter) {
                                   this.addAndFillCell(playerRow, player.getAge(), __COLOR, null, 2);
                               }
                               if (__WARNDRAW.monthDraw()) {  // Abrechnungszeitraum vor dem letztmoeglichen Ziehen...
                                   formatCell(playerRow.cells[this.colIdx.Age], true, __WARNDRAW.colAlert, null, 1.0);
                               }
                               if (this.fix) {
                                   this.addAndFillCell(playerRow, player.getFixSkills(), __COLOR, null, 0);
                               }
                               if (this.tr) {
                                   this.addAndFillCell(playerRow, player.getTrainableSkills(), __COLOR, null, 0);
                               }
                               if (this.zat) {
                                   this.addAndFillCell(playerRow, player.getZatDone(), __COLOR, null, 0);
                               }
                               if (this.antHpt) {
                                   this.addAndFillCell(playerRow, player.getPriPercent(player.getPos()), __COLOR, null, 0);
                               }
                               if (this.antNeb) {
                                   this.addAndFillCell(playerRow, player.getSecPercent(player.getPos()), __COLOR, null, 0);
                               }
                               if (this.pri) {
                                   this.addAndFillCell(playerRow, player.getPrios(player.getPos()), __COLOR, null, 1);
                               }
                               if (this.skill) {
                                   this.addAndFillCell(playerRow, player.getSkill(), __COLOR, null, 2);
                               }
                               if (this.pos) {
                                   this.addAndFillCell(playerRow, player.getPos(), __POS1COLOR);
                               }
                               for (let i = 1; i <= 6; i++) {
                                   const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
                                   const __COLI = getColor(__POSI);

                                   if (i <= this.anzOpti) {
                                       if ((i === 1) || ! player.isGoalie) {
                                           // Opti anzeigen
                                           this.addAndFillCell(playerRow, player.getOpti(__POSI), __COLI, null, 2);
                                       } else {
                                           // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
                                           this.addCell(playerRow);
                                       }
                                   }
                                   if (i <= this.anzMw) {
                                       if ((i === 1) || ! player.isGoalie) {
                                           // MW anzeigen
                                           this.addAndFillCell(playerRow, player.getMarketValue(__POSI), __COLI, null, 0);
                                       } else {
                                           // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                                           this.addCell(playerRow);
                                       }
                                   }
                               }

                               // Einzelwerte mit Ende 18
                               if (this.colIdx.Einz) {
                                   if (this.substSkills) {
                                       convertArrayFromHTML(playerRow.cells, this.colIdx.Einz, player.skillsEnd, function(value, cell, unused, index) {
                                                                                                                     if (~ __IDXPRI.indexOf(index)) {
                                                                                                                         formatCell(cell, true, __OSBLAU, __POS1COLOR, 1.0);
                                                                                                                     }
                                                                                                                     return value;
                                                                                                                 });
                                   } else {
                                       convertArrayFromHTML(playerRow.cells, this.colIdx.Einz, player.skills.length, function(value, cell, unused, index) {
                                                                                                                         if (~ __IDXPRI.indexOf(index)) {
                                                                                                                             formatCell(cell, true, __POS1COLOR, null, 1.0);
                                                                                                                         }
                                                                                                                         return value;
                                                                                                                     });
                                   }
                               }
                               if (this.trE) {
                                   this.addAndFillCell(playerRow, player.getTrainableSkills(player.__TIME.end), __COLOR, null, 1);
                               }
                               if (this.zatE) {
                                   this.addAndFillCell(playerRow, player.getZatLeft(), __COLOR, null, 0);
                               }
                               if (this.antHptE) {
                                   this.addAndFillCell(playerRow, player.getPriPercent(player.getPos(), player.__TIME.end), __COLOR, null, 0);
                               }
                               if (this.antNebE) {
                                   this.addAndFillCell(playerRow, player.getSecPercent(player.getPos(), player.__TIME.end), __COLOR, null, 0);
                               }
                               if (this.priE) {
                                   this.addAndFillCell(playerRow, player.getPrios(player.getPos(), player.__TIME.end), __COLOR, null, 1);
                               }
                               if (this.skillE) {
                                   this.addAndFillCell(playerRow, player.getSkill(player.__TIME.end), __COLOR, null, 2);
                               }
                               for (let i = 1; i <= 6; i++) {
                                   const __POSI = ((i === 1) ? player.getPos() : player.getPos(i));
                                   const __COLI = getColor(__POSI);

                                   if (i <= this.anzOptiE) {
                                       if ((i === 1) || ! player.isGoalie) {
                                           // Opti anzeigen
                                           this.addAndFillCell(playerRow, player.getOpti(__POSI, player.__TIME.end), __COLI, null, 2);
                                       } else {
                                           // TOR, aber nicht bester Opti -> nur Zelle hinzufuegen
                                           this.addCell(playerRow);
                                       }
                                   }
                                   if (i <= this.anzMwE) {
                                       if ((i === 1) || ! player.isGoalie) {
                                           // MW anzeigen
                                           this.addAndFillCell(playerRow, player.getMarketValue(__POSI, player.__TIME.end), __COLI, null, 0);
                                       } else {
                                           // TOR, aber nicht bester MW -> nur Zelle hinzufuegen
                                           this.addCell(playerRow);
                                       }
                                   }
                               }
                           },  // Ende addValues(player, playerRow)
        'setGroupTitle'  : function(tableRow) {
                               if (this.gtUxx) {
                                   const __CELL = tableRow.cells[0];
                                   const __SAI = __CELL.innerHTML.match(/Saison (\d+)/)[1];
                                   const __JG = 13 + this.saison - __SAI;

                                   __CELL.innerHTML = __CELL.innerHTML.replace('Jahrgang', 'U' + __JG + ' - $&');
                               }

                               tableRow.style.display = (this.gt ? '' : 'none');
                           }  // Ende setGroupTitle(tableRow)
    });

// Klasse PlayerRecord ******************************************************************

function PlayerRecord(land, age, isGoalie, saison, currZAT, donation) {
    'use strict';

    this.land = land;
    this.age = age;
    this.isGoalie = isGoalie;

    this.saison = saison;
    this.currZAT = currZAT;
    this.donation = donation;
    this.mwFormel = ((this.saison < 10) ? this.__MWFORMEL.alt : this.__MWFORMEL.S10);

    // in new PlayerRecord() definiert:
    // this.land: TLA des Geburtslandes
    // this.age: Ganzzahliges Alter des Spielers
    // this.isGoalie: Angabe, ob es ein TOR ist
    // this.mwFormel: Benutzte MW-Formel, siehe __MWFORMEL
    // this.donation: Jugendfoerderungsbetrag in Euro

    // in this.initPlayer() definiert:
    // this.zatGeb: ZAT, an dem der Spieler Geburtstag hat, -1 fuer "noch nicht zugewiesen", also '?'
    // this.zatAge: Bisherige erfolgte Trainings-ZATs
    // this.birth: Universell eindeutige Nummer des Geburtstags-ZATs des Spielers
    // this.talent: Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
    // this.aufwert: Aufwertungsstring

    // in this.calcSkills() definiert:
    // this.positions[][]: Positionstexte und Optis; TOR-Index ist 5
    // this.skills[]: Einzelskills
    // this.skillsEnd[]: Berechnet aus this.skills, this.age und aktuellerZat
    // this.zatLeft: ZATs bis zum Ende 18 (letzte Ziehmoeglichkeit)
    // this.restEnd: Korrekturterm zum Ausgleich von Rundungsfehlern mit Ende 18
    //               (also Skills, die nicht explizit in this.skillsEnd stehen)

    // in this.calcZusatz()/setZusatz() definiert:
    // this.trainiert: Anzahl der erfolgreichen Trainingspunkte
    // indirekt this.zatAge und this.bestPos

    // in this.createWarnDraw() definiert:
    // this.warnDraw: Behandlung von Warnungen Ende 18
    // this.warnDrawAufstieg: Behandlung von Warnungen bei Aufstieg

    // in this.getPos() definiert:
    // this.bestPos: erster (bester) Positionstext
}

Class.define(PlayerRecord, Object, {
        '__TIME'                : {   // Zeitpunktangaben
                                      'cre' : 0,  // Jugendspieler angelegt (mit 12 Jahren)
                                      'beg' : 1,  // Jugendspieler darf trainieren (wird 13 Jahre alt)
                                      'now' : 2,  // Aktueller ZAT
                                      'end' : 3   // Jugendspieler wird Ende 18 gezogen (Geb. - 1 bzw. ZAT 71 fuer '?')
                                  },
        '__MWFORMEL'            : {   // Zu benutzende Marktwertformel
                                      'alt' : 0,  // Marktwertformel bis Saison 9 inklusive
                                      'S10' : 1   // Marktwertformel MW5 ab Saison 10
                                  },
        '__MAXPRISKILLS'        : 4 * 99,
        'toString'              : function() {  // Bisher nur die noetigsten Werte ausgegeben...
                                      let result = "Alter\t\t" + this.age + "\n\n";
                                      result += "Aktuelle Werte\n";
                                      result += "Skillschnitt\t" + this.getSkill().toFixed(2) + '\n';
                                      result += "Optis und Marktwerte";

                                      for (let pos of this.positions) {
                                          result += "\n\t" + pos + '\t';
                                          result += this.getOpti(pos).toFixed(2) + '\t';
                                          result += getNumberString(this.getMarketValue(pos).toString());
                                      }

                                      result += "\n\nWerte mit Ende 18\n";
                                      result += "Skillschnitt\t" + this.getSkill(this.__TIME.end).toFixed(2) + '\n';
                                      result += "Optis und Marktwerte";

                                      for (let pos of this.positions) {
                                          result += "\n\t" + this.getPos()[i] + '\t';
                                          result += this.getOpti(pos, this.__TIME.end).toFixed(2) + '\t';
                                          result += getNumberString(this.getMarketValue(pos, this.__TIME.end).toString());
                                      }

                                      return result;
                                  },  // Ende this.toString()
        'initPlayer'            : function(data, index, isSkillData = false) {  // isSkillData: true = Skilldaten, false = Basiswerte (Geb., Talent, Aufwertungen) oder keine
                                      if (data !== undefined) {
                                          if (isSkillData) {
                                              this.setSkills(data[index]);
                                          } else if (data.length >= 2){
                                              this.setGeb(data[0][index]);
                                              this.talent = data[1][index];
                                              this.aufwert = data[2][index];
                                          } else {
                                              // keine Daten
                                          }
                                      }
                                  },  // Ende this.initPlayer()
        'createWarnDraw'        : function(ziehmich = null, klasse = 1) {  // ziehmich: input Element zum Ziehen; klasse: Spielklasse 1, 2, 3
                                      // Objekte fuer die Verwaltung der Ziehwarnungen...
                                      this.warnDraw = undefined;
                                      this.warnDrawAufstieg = undefined;
                                      if (ziehmich) {
                                          const __LASTZAT = this.currZAT + this.getZatLeft();

                                          if (__LASTZAT < 72) {  // U19
                                              this.warnDraw = new WarnDrawPlayer(this, getColor('STU'));  // rot
                                              __LOG[4](this.getAge().toFixed(2), "rot");
                                          } else if (__LASTZAT < Math.max(2, klasse) * 72) {  // Rest bis inkl. U18 (Liga 1 und 2) bzw. U17 (Liga 3)
                                              // do nothing
                                          } else if (__LASTZAT < (klasse + 1) * 72) {  // U17/U16 je nach Liga 2/3
                                              this.warnDrawAufstieg = new WarnDrawPlayer(this, getColor('OMI'));  // magenta
                                              this.warnDrawAufstieg.setAufstieg();
                                              __LOG[4](this.getAge().toFixed(2), "magenta");
                                          }
                                      }
                                  },  // Ende this.createWarnDraw()
        'setSkills'             : function(skills) {
                                      // Berechnet die Opti-Werte, sortiert das Positionsfeld und berechnet die Einzelskills mit Ende 18
                                     this.skills = skills;

                                      const __POSREIHEN = [ 'ABW', 'DMI', 'MIT', 'OMI', 'STU', 'TOR' ];
                                      this.positions = [];
                                      for (let index = 0; index < __POSREIHEN.length; index++) {
                                          const __REIHE = __POSREIHEN[index];

                                          this.positions[index] = [ __REIHE, this.getOpti(__REIHE) ];
                                      }

                                      // Sortieren
                                      sortPositionArray(this.positions);
                                  },  // Ende this.setSkills()
        'prognoseSkills'        : function() {
                                      // Einzelskills mit Ende 18 berechnen
                                      this.skillsEnd = [];

                                      const __ZATDONE = this.getZatDone();
                                      const __ZATTOGO = this.getZatLeft();
                                      const __ADDRATIO = (__ZATDONE ? __ZATTOGO / __ZATDONE : 0);

                                      let addSkill = __ZATTOGO * this.getAufwertungsSchnitt();

                                      for (let i in this.skills) {
                                          const __SKILL = this.skills[i];
                                          let progSkill = __SKILL;

                                          if (isTrainableSkill(i)) {
                                              // Auf ganze Zahl runden und parseInt(), da das sonst irgendwie als String interpretiert wird
                                              const __ADDSKILL = Math.min(99 - progSkill, getMulValue(__ADDRATIO, __SKILL, 0, NaN));

                                              progSkill += __ADDSKILL;
                                              addSkill -= __ADDSKILL;
                                          }

                                          this.skillsEnd[i] = progSkill;
                                      }
                                      this.restEnd = addSkill;
                                  },  // Ende this.prognoseSkills()
        'setZusatz'             : function(zatAge, trainiert, bestPos) {
                                      // Setzt Nebenwerte fuer den Spieler (geht ohne initPlayer())
                                      if (zatAge !== undefined) {
                                          this.zatAge = zatAge;
                                      }
                                      this.trainiert = trainiert;
                                      this.bestPos = bestPos;
                                  },
        'calcZusatz'            : function() {
                                      // Ermittelt Nebenwerte fuer den Spieler und gibt sie alle zurueck (nach initPlayer())
                                      // this.zatAge und this.skills bereits in initPlayer() berechnet
                                      this.trainiert = this.getTrainiert(true);  // neu berechnet aus Skills
                                      let bestPos = this.getPos(-1);  // hier: -1 explizit angeben, damit neu ermittelt (falls this.bestPos noch nicht belegt)

                                      return {
                                                 'zatAge'     : this.zatAge,
                                                 'trainiert'  : this.trainiert,
                                                 'bestPos'    : bestPos
                                             };
                                  },
        'getGeb'                : function() {
                                      return (this.zatGeb < 0) ? '?' : this.zatGeb;
                                  },
        'setGeb'                : function(gebZAT) {
                                      this.zatGeb = gebZAT;
                                      this.zatAge = this.calcZatAge(this.currZAT);
                                      this.birth = (36 + this.saison) * 72 + this.currZAT - this.zatAge;
                                  },
        'calcZatAge'            : function(currZAT) {
                                      let zatAge;

                                      if (this.zatGeb !== undefined) {
                                          let ZATs = 72 * (this.age - ((currZAT < this.zatGeb) ? 12 : 13));  // Basiszeit fuer die Jahre seit Jahrgang 13

                                          if (this.zatGeb < 0) {
                                              zatAge = ZATs + currZAT;  // Zaehlung begann Anfang der Saison (und der Geburtstag wird erst nach dem Ziehen bestimmt)
                                          } else {
                                              zatAge = ZATs + currZAT - this.zatGeb;  // Verschiebung relativ zum Geburtstag (von -zatGeb, ..., 0, ..., 71 - zatGeb)
                                          }
                                      }

                                      return zatAge;
                                  },
        'getZatAge'             : function(when = this.__TIME.now) {
                                      if (when === this.__TIME.end) {
                                          return (18 - 12) * 72 - 1;  // (max.) Trainings-ZATs bis Ende 18
                                      } else if (this.zatAge !== undefined) {
                                          return this.zatAge;
                                      } else {
                                          __LOG[4]("Empty getZatAge()");

                                          return NaN;
                                      }
                                  },
        'getZatDone'            : function(when = this.__TIME.now) {
                                      return Math.max(0, this.getZatAge(when));
                                  },
        'getZatLeft'            : function(when = this.__TIME.now) {
                                      if (this.zatLeft === undefined) {
                                          this.zatLeft = this.getZatDone(this.__TIME.end) - this.getZatDone(when);
                                      }

                                      return this.zatLeft;
                                  },
        'calcZiehIndex'         : function() {
                                      //const __RESTZAT = this.getZatAge(this.__TIME.end) - this.getZatAge() + this.currZAT;
                                      //const __INDEX = parseInt(__RESTZAT / 6 + 1) - 1;  // Lfd. Nummer des Abrechnungsmonats (0-basiert)

                                      return (this.warnDraw && this.warnDraw.calcZiehIndex(this.currZAT));
                                  },
        'isZiehAufstieg'        : function() {
                                      return (this.warnDrawAufstieg && this.warnDrawAufstieg.isZiehAufstieg());
                                  },
        'getAge'                : function(when = this.__TIME.now) {
                                      if (this.mwFormel === this.__MWFORMEL.alt) {
                                          return (when === this.__TIME.end) ? 18 : this.age;
                                      } else {  // Geburtstage ab Saison 10...
                                          return (13.00 + this.getZatAge(when) / 72);
                                      }
                                  },
        'getTrainiert'          : function(recalc = false) {
                                      if (recalc || (this.trainiert === undefined)) {
                                          this.trainiert = this.getTrainableSkills();
                                      }

                                      return this.trainiert;
                                  },
        'getAufwertungsSchnitt' : function() {
                                      const __ZATDONE = this.getZatDone();

                                      if (__ZATDONE) {
                                          return parseFloat(this.getTrainiert() / __ZATDONE);
                                      } else {
                                          // Je nach Talentklasse mittlerer Aufwertungsschnitt aller Talente der Klasse
                                          // (gewichtet nach Verteilung der Talentstufen in dieser Talentklasse)
                                          return (1 + (this.talent / 3.6)) * (this.donation / 10000);
                                      }
                                  },
        'getPos'                : function(idx = 0) {
                                      const __IDXOFFSET = 1;

                                      switch (idx) {
                                      case -1 : return (this.bestPos = this.positions[this.isGoalie ? 5 : 0][0]);
                                      case  0 : return this.bestPos;
                                      default : return this.positions[idx - __IDXOFFSET][0];
                                      }
                                  },
        'getPosPercent'         : function(idx = 0) {
                                      const __IDXOFFSET = 1;
                                      const __OPTI = this.positions[this.isGoalie ? 5 : 0][1];
                                      let optiSec = __OPTI;

                                      switch (idx) {
                                      case -1 : break;  // __OPTI
                                      case  0 : optiSec = (this.isGoalie ? 0 : this.positions[1][1]);  // Backup-Wert (TOR: keiner)
                                                break;
                                      default : optiSec = this.positions[idx - __IDXOFFSET][1];
                                      }

                                      return parseFloat(100 * optiSec / __OPTI);
                                  },
        'getTalent'             : function() {
                                      return (this.talent < 0) ? 'wenig' : (this.talent > 0) ? 'hoch' : 'normal';
                                  },
        'getAufwert'            : function() {
                                      return this.aufwert;
                                  },
        'boldPriSkillNames'     : function(text) {
                                      const __PRISKILLNAMES = this.getPriSkillNames();

                                      return (! text) ? text : text.replace(/\w+/g, function(name) {
                                                                                        return ((~ __PRISKILLNAMES.indexOf(name)) ? '<b>' + name + '</b>' : name);
                                                                                    });
                                  },
        'getPriSkillNames'      : function(pos = undefined) {
                                      return getSkillNameArray(getIdxPriSkills(pos ? pos : this.getPos()), this.isGoalie);
                                  },
        'getSkillSum'           : function(when = this.__TIME.now, idxSkills = undefined, restRate = 15) {
                                      let cachedItem;

                                      if (idxSkills === undefined) {  // Gesamtsumme ueber alle Skills wird gecached...
                                          cachedItem = ((when === this.__TIME.end) ? 'skillSumEnd' : 'skillSum');

                                          const __CACHED = this[cachedItem];

                                          if (__CACHED !== undefined) {
                                              return __CACHED;
                                          }

                                          idxSkills = getIdxAllSkills();
                                      }

                                      const __SKILLS = ((when === this.__TIME.end) ? this.skillsEnd : this.skills);
                                      let sumSkills = ((when === this.__TIME.end) ? (restRate / 15) * this.restEnd : 0);

                                      if (__SKILLS) {
                                          for (let idx of idxSkills) {
                                              sumSkills += __SKILLS[idx];
                                          }
                                      }

                                      if (cachedItem !== undefined) {
                                          this[cachedItem] = sumSkills;
                                      }

                                      return sumSkills;
                                  },
        'getSkill'              : function(when = this.__TIME.now) {
                                      return this.getSkillSum(when) / 17;
                                  },
        'getOpti'               : function(pos, when = this.__TIME.now) {
                                      const __SUMALLSKILLS = this.getSkillSum(when);
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);
/*if (this.zatGeb === 24) {
    console.error("__OVERFLOW = " + __OVERFLOW);
    console.error("__SUMALLSKILLS = " + __SUMALLSKILLS);
    console.error("__SUMPRISKILLS = " + __SUMPRISKILLS);
    console.error("getOpti(" + pos + ") = " + ((4 * (__SUMPRISKILLS - __OVERFLOW) + __SUMALLSKILLS) / 27));
}*/
                                      return (4 * (__SUMPRISKILLS - __OVERFLOW) + __SUMALLSKILLS) / 27;
                                  },
        'getPrios'              : function(pos, when = this.__TIME.now) {
                                      return Math.min(this.__MAXPRISKILLS, this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4)) / 4;
                                  },
        'getPriPercent'         : function(pos, when = this.__TIME.now) {
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __SUMSECSKILLS = this.getSkillSum(when, getIdxSecSkills(pos), 7);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);

                                      return (100 * (__SUMPRISKILLS - __OVERFLOW)) / (__SUMPRISKILLS + __SUMSECSKILLS);
                                  },
        'getSecPercent'         : function(pos, when = this.__TIME.now) {
                                      const __SUMPRISKILLS = this.getSkillSum(when, getIdxPriSkills(pos), 2 * 4);
                                      const __SUMSECSKILLS = this.getSkillSum(when, getIdxSecSkills(pos), 7);
                                      const __OVERFLOW = Math.max(0, __SUMPRISKILLS - this.__MAXPRISKILLS);

                                      return (100 * (__SUMSECSKILLS + __OVERFLOW)) / (__SUMPRISKILLS + __SUMSECSKILLS);
                                  },
        'getTrainableSkills'    : function(when = this.__TIME.now) {
                                      return this.getSkillSum(when, getIdxTrainableSkills());
                                  },
        'getFixSkills'          : function() {
                                      return this.getSkillSum(this.__TIME.now, getIdxFixSkills());
                                  },
        'getMarketValue'        : function(pos, when = this.__TIME.now) {
                                      const __AGE = this.getAge(when);

                                      if (this.mwFormel === this.__MWFORMEL.alt) {
                                          return Math.round(Math.pow((1 + this.getSkill(when)/100) * (1 + this.getOpti(pos, when)/100) * (2 - __AGE/100), 10) * 2);    // Alte Formel bis Saison 9
                                      } else {  // MW-Formel ab Saison 10...
                                          const __MW5TF = 1.00;  // Zwischen 0.97 und 1.03

                                          return Math.round(Math.pow(1 + this.getSkill(when)/100, 5.65) * Math.pow(1 + this.getOpti(pos, when)/100, 8.1) * Math.pow(1 + (100 - __AGE)/49, 10) * __MW5TF);
                                      }
                                  },
        'getFingerPrint'        : function() {
                                      // Jeweils gleichbreite Werte: (Alter/Geb.=>Monat), Land, Talent ('-', '=', '+')...
                                      const __BASEPART = padNumber(this.birth / 6, 3) + padLeft(this.land, -3);
                                      const __TALENT = '-=+'[this.talent + 1];

                                      if (this.skills === undefined) {
                                          return __BASEPART + getValue(__TALENT, "");
                                      } else {
                                          const __SKILLS = this.skills;
                                          const __FIXSKILLS = getIdxFixSkills().slice(-4);  // ohne die Nullen aus FUQ und ERF
                                          const __FIXSKILLSTR = __FIXSKILLS.map(function(idx) {
                                                                                    return padNumber(__SKILLS[idx], -2);
                                                                                }).join("");

                                          // Jeweils gleichbreite Werte: Zusaetzlich vier der sechs Fixskills...
                                          return (__BASEPART + getValue(__TALENT, '?') + __FIXSKILLSTR);
                                      }
                                  },
        'isFingerPrint'         : function(fpA, fpB) {
                                      if (fpA && fpB) {
                                          if (fpA === fpB) {
                                              return true;  // voellig identisch
                                          } else if (this.isBaseFingerPrint(fpA, fpB)) {
                                              return 1;  // schwaches true
                                          }
                                      }

                                      return false;
                                  },
        'isBaseFingerPrint'     : function(fpA, fpB) {
                                      if (fpA && fpB) {
                                          if (this.getBaseFingerPrint(fpA) === this.getBaseFingerPrint(fpB)) {
                                              // Base ist identisch...
                                              if ((getValue(fpA[6], '?') === '?') || (getValue(fpB[6], '?') === '?') || (fpA[6] === fpB[6])) {
                                                  // ... und auch das Talent-Zeichen ist leer oder '?'...
                                                  return true;
                                              }
                                          }
                                      }

                                      return false;
                                  },
        'getBaseFingerPrint'    : function(fingerprint) {
                                      return (fingerprint ? fingerprint.slice(0, 6) : undefined);
                                  },
        'getCatFromFingerPrint' : function(fingerprint) {
                                      return (fingerprint ? floorValue((fingerprint.slice(0, 3) - 1) / 12) : undefined);
                                  },
        'getCat'                : function() {
                                      return (this.birth ? floorValue((this.birth - 1) / 72) : undefined);
                                  },
        'findInFingerPrints'    : function(fingerprints) {
                                      const __MYFINGERPRINT = this.getFingerPrint();  // ggfs. unvollstaendiger Fingerprint
                                      const __MYCAT = this.getCat();
                                      const __RET = [];

                                      if (__MYCAT !== undefined) {
                                          for (let id in fingerprints) {
                                              const __CAT = this.getCatFromFingerPrint(id);

                                              if (__CAT === __MYCAT) {
                                                  if (this.isFingerPrint(id, __MYFINGERPRINT)) {
                                                      __RET.push(id);
                                                      break;  // erster Treffer zaehlt
                                                  }
                                              }
                                          }
                                      }

                                      return ((__RET.length === 1) ? __RET[0] : undefined);
                                  }
    });

// Klasse WarnDrawPlayer *****************************************************************

function WarnDrawPlayer(player, alertColor) {
    'use strict';

    this.player = player;

    if (this.player !== undefined) {
        // Default Warnlevel...
        this.setZatLeft(player.getZatLeft());
        this.currZAT = player.currZAT;
        this.setWarn(true, true, true);
        this.colAlert = alertColor || this.alertColor();
    } else {
        // Kein Warnlevel...
        this.setZatLeft(undefined);
        this.currZAT = undefined;
        this.setWarn(false, false, false);
        this.colAlert = undefined;
    }
}

Class.define(WarnDrawPlayer, Object, {
        '__MONATEBISABR'    : 1,
        '__ZATWARNVORLAUF'  : 1,
        '__ZATMONATVORLAUF' : 6,
        'setZatLeft'        : function(zatLeft) {
                                  this.zatLeft = zatLeft;
                              },
        'setWarn'           : function(warn, warnMonth, warnAufstieg) {
                                  this.warn = (this.aufstieg ? warnAufstieg : warn);
                                  this.warnMonth = warnMonth;
                              },
        'alertColor'        : function() {
                                  return getColor('STU');  // rot
                              },
        'getColor'          : function(color) {
                                  return ((this.mustDraw() && this.colAlert) ? this.colAlert : color);
                              },
        'calcZiehIndex'     : function(currZAT) {
                                  const __RESTZAT = this.zatLeft + currZAT;
                                  const __INDEX = parseInt(__RESTZAT / 6 + 1) - this.__MONATEBISABR;  // Lfd. Nummer des Abrechnungsmonats (0-basiert)

                                  return __INDEX;
                              },
        'isZiehAufstieg'    : function() {
                                  return this.aufstieg;
                              },
        'setAufstieg'       : function() {
                                  this.aufstieg = true;

                                  if (this.isZiehAufstieg()) {
                                      this.setZatLeft(72 - this.currZAT - this.__ZATWARNVORLAUF);
                                  }

                                  return this.zatLeft;
                              },
        'mustDraw'          : function() {
                                  return ((this.warn || this.warnMonth) && (this.zatLeft < this.__ZATWARNVORLAUF));
                              },
        'monthDraw'         : function() {
                                  return (this.mustDraw() || (this.warn && (this.aufstieg || this.warnMonth) && (this.zatLeft < this.__ZATMONATVORLAUF)));  // Abrechnungszeitraum vor dem letztmoeglichen Ziehen...
                              }
    });

const __NOWARNDRAW = new WarnDrawPlayer(undefined, undefined);  // inaktives Objekt

// Klasse WarnDrawMessage *****************************************************************

function WarnDrawMessage(optSet, currZAT) {
    'use strict';

    this.optSet = optSet;

    this.warn = getOptValue(this.optSet.zeigeWarnung, true);
    this.warnMonth = getOptValue(this.optSet.zeigeWarnungMonat, true);
    this.warnHome = getOptValue(this.optSet.zeigeWarnungHome, true);
    this.warnDialog = getOptValue(this.optSet.zeigeWarnungDialog, false);
    this.warnAufstieg = getOptValue(this.optSet.zeigeWarnungAufstieg, true);
    this.warnLegende = getOptValue(this.optSet.zeigeWarnungLegende, true);

    this.out = {
                   'supertag' : true,
                   'top'      : true,
                   'link'     : true,
                   'label'    : true,
                   'bottom'   : true
               };

    this.setOptionHome();

    this.startMessage(currZAT);
}

Class.define(WarnDrawMessage, Object, {
        '__ZATWARNVORLAUF'  : 1,
        '__ZATMONATVORLAUF' : 6,
        'startMessage'      : function(currZAT) {
                                  this.setZat(currZAT);
                                  this.createMessage();
                              },
        'setZat'            : function(currZAT) {
                                  this.currZAT = currZAT;

                                  if (currZAT === undefined) {
                                      this.abrZAT = undefined;
                                      this.rest   = undefined;
                                      this.anzahl = undefined;
                                  } else {
                                      this.configureZat();
                                  }
                              },
        'setOptionHome'     : function() {
                                  this.warnOption = this.hasHome();
                              },
        'setOptionLegende'  : function() {
                                  this.warnOption = this.hasLegende();
                              },
        'configureZat'      : function() {
                                  const __ZIEHANZAHL = getOptValue(this.optSet.ziehAnz, []);
                                  const __INDEX = parseInt(this.currZAT / 6);

                                  this.abrZAT = (__INDEX + 1) * 6;
                                  this.rest   = 5 - (this.currZAT % 6);
                                  this.anzahl = __ZIEHANZAHL[__INDEX];
                              },
        'getTextMessage'    : function() {
                                  return "ZAT " + this.abrZAT + ' ' + ((this.anzahl > 1) ? "m\xFCssen " + this.anzahl : "muss einer") +
                                         " deiner Jugendspieler in das Profiteam \xFCbernommen werden, ansonsten verschwinde" + ((this.anzahl > 1) ? "n sie" : "t er") + '!';
                              },
        'createMessage'     : function() {
                                  this.label = undefined;
                                  this.when = undefined;
                                  this.text = undefined;

                                  if (this.hasHome() || this.hasLegende() || this.hasDialog()) {
                                      if (this.anzahl > 0) {
                                          this.text = this.getTextMessage();

                                          if (this.warnMonth && (this.rest > 0)) {
                                              this.label = "Warnung";
                                              this.when = "Bis zur n\xE4chsten Abrechnung am ";
                                          } else if ((this.warn || this.warnMonth) && (this.rest === 0)) {
                                              this.label = "LETZTE WARNUNG VOR DER ABRECHNUNG";
                                              this.when = "Bis zum n\xE4chsten ";
                                          }
                                      }
                                  }
                              },
        'hasMessage'        : function() {
                                  return !! this.when;
                              },
        'hasHome'           : function() {
                                  return this.warnHome;
                              },
        'hasLegende'        : function() {
                                  return this.warnLegende;
                              },
        'hasOption'         : function() {
                                  return this.warnOption;
                              },
        'hasDialog'         : function() {
                                  return this.warnDialog;
                              },
        'showMessage'       : function(anchor, tag, appendFind = true) {  // appendFind: true = append, false = insertBefore, "..." search string = insert at find position
                                  let ret = (anchor || { }).innerHTML;

                                  if (this.hasMessage()) {
                                      if (this.hasOption()) {
                                          const __OLDHTML = ret;
                                          const __HTML = this.getHTML(tag);

                                          if ((typeof appendFind) === 'string') {
                                              const __INDEX = __OLDHTML.indexOf(appendFind);
                                              const __POS = (~ __INDEX) ? __INDEX : __OLDHTML.length;

                                              ret = __OLDHTML.substring(0, __POS) + __HTML + __OLDHTML.substring(__POS);
                                          } else if (appendFind) {
                                              ret = __OLDHTML + __HTML;
                                          } else {
                                              ret = __HTML + __OLDHTML;
                                          }

                                          anchor.innerHTML = ret;
                                      }
                                  }

                                  return ret;
                              },
        'showDialog'        : function(dlgFun) {
                                  if (this.hasMessage()) {
                                      if (this.hasDialog() && (this.rest === 0)) {
                                          dlgFun(this.label, this.when + this.text);
                                      }
                                  }
                              },
        'tagText'           : function(tag, text) {
                                  return ((tag !== undefined) ? this.getOpeningTag(tag) + text + this.getClosingTag(tag) : text);
                              },
        'tagParagraph'      : function(tag, text) {
                                  return this.tagText(tag, this.tagText(this.getSubTag(tag), text));
                              },
        'getSubTag'         : function(tag) {
                                  return ((tag === 'tr') ? 'td' + this.getColorTD() : ((tag === 'p') ? this.getColorTag() : undefined));
                              },
        'getSuperTag'       : function(tag) {
                                  return ((tag === 'p') ? 'div' : undefined);
                              },
        'getOpeningTag'     : function(tag) {
                                  return '<' + tag + '>';
                              },
        'getClosingTag'     : function(tag) {
                                  const __INDEX1 = (tag ? tag.indexOf(' ') : -1);
                                  const __INDEX2 = (tag ? tag.indexOf('=') : -1);
                                  const __INDEX = ((~ __INDEX1) && (~ __INDEX2)) ? Math.min(__INDEX1, __INDEX2) : Math.max(__INDEX1, __INDEX2);
                                  const __TAGNAME = ((~ __INDEX) ? tag.substring(0, __INDEX) : tag);

                                  return "</" + __TAGNAME + '>';
                              },
        'getLink'           : function() {
                                  return './ju.php';
                              },
        'getTopHTML'        : function(tag) {
                                  return this.tagParagraph(tag, " ");
                              },
        'getBottomHTML'     : function(tag) {
                                  return this.tagParagraph(tag, " ");
                              },
        'getColorTag'       : function() {
                                  return "color='red'";  // rot
                              },
        'getColorTD'        : function() {
                                  return " class='STU'";  // rot
                              },
        'getHTML'           : function(tag = 'p') {
                                  return this.tagParagraph((this.out.supertag ? this.getSuperTag(tag) : undefined), (this.out.top ? this.getTopHTML(tag) : "") +
                                         this.tagParagraph(tag, this.tagText('b', this.tagText((this.out.link ? "a href='" + this.getLink() + "'" : undefined),
                                         (this.out.label ? this.label + ": " : "") + this.when + this.text))) + (this.out.bottom ? this.getBottomHTML(tag) : ""));
                              }
    });

Object.defineProperty(WarnDrawMessage.prototype, 'innerHTML', {
        get : function() {
                  return this.getHTML('p');
              }
    });

// Klasse WarnDrawMessageAufstieg *****************************************************************

function WarnDrawMessageAufstieg(optSet, currZAT) {
    'use strict';

    WarnDrawMessage.call(this, optSet, currZAT);

    this.out.top = false;  // kein Vorschub vor der Zeile

    this.warn = (this.warn && this.warnAufstieg);  // kann man ausschalten
    this.startMessage(currZAT);  // 2. Aufruf (zur Korrektur)
}

Class.define(WarnDrawMessageAufstieg, WarnDrawMessage, {
        'configureZat'      : function() {
                                  const __ZIEHANZAUFSTIEG = getOptValue(this.optSet.ziehAnzAufstieg, 0);
                                  const __INDEX = parseInt(this.currZAT / 6);

                                  this.abrZAT = (__INDEX + 1) * 6;
                                  this.rest   = 5 - (this.currZAT % 6);
                                  this.anzahl = ((this.currZAT + this.__ZATMONATVORLAUF > 72 - this.__ZATWARNVORLAUF) ? __ZIEHANZAUFSTIEG : 0);

                                  this.warnDialog = false;     // kein Dialog fuer Aufstiegswarnung
                                  this.warnMonth = this.warn;  // nur im letzten Monat der Saison!
                              },
        'getTextMessage'    : function() {
                                  return "ZAT " + this.abrZAT + " ist im Falle eines Aufstiegs f\xFCr " + ((this.anzahl > 1) ? "" + this.anzahl : "einen") +
                                         " deiner Jugendspieler m\xF6glicherweise die letzte Chance, " + ((this.anzahl > 1) ? " diese noch vor ihrem" : "ihn noch vor seinem") +
                                         " Geburtstag in der n\xE4chsten Saison in das Profiteam zu \xFCbernehmen!";
                              },
        'getColorTag'       : function() {
                                  return "color='magenta'";  // magenta
                              },
        'getColorTD'        : function() {
                                  return " class='OMI'";  // magenta
                              }
    });

// Ende Hilfs-Klassen *****************************************************************

// Funktionen fuer die HTML-Seite *******************************************************

// Liest eine Zahl aus der Spalte einer Zeile der Tabelle aus (z.B. Alter, Geburtsdatum)
// cells: Die Zellen einer Zeile
// colIdxInt: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Zahl (-1 fuer "keine Zahl", undefined fuer "nicht gefunden")
function getIntFromHTML(cells, colIdxInt) {
    const __CELL = getValue(cells[colIdxInt], { });
    const __TEXT = __CELL.textContent;

    if (__TEXT !== undefined) {
        try {
            const __VALUE = parseInt(__TEXT, 10);

            if (! isNaN(__VALUE)) {
                return __VALUE;
            }
        } catch (ex) { }

        return -1;
    }

    return undefined;
}

// Liest eine Dezimalzahl aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxInt: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Dezimalzahl (undefined fuer "keine Zahl" oder "nicht gefunden")
function getFloatFromHTML(cells, colIdxFloat) {
    const __CELL = getValue(cells[colIdxFloat], { });
    const __TEXT = __CELL.textContent;

    if (__TEXT !== undefined) {
        try {
            return parseFloat(__TEXT);
        } catch (ex) { }
    }

    return undefined;
}

// Liest einen String aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Spalteneintrag als String ("" fuer "nicht gefunden")
function getStringFromHTML(cells, colIdxStr) {
    const __CELL = getValue(cells[colIdxStr], { });
    const __TEXT = __CELL.textContent;

    return getValue(__TEXT.toString(), "");
}

// Liest ein erstes Element aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Spalteneintrag als Element (null fuer "nicht gefunden")
function getElementFromHTML(cells, colIdxStr) {
    const __CELL = getValue(cells[colIdxStr], { });

    return __CELL.firstElementChild;
}

// Liest die Talentklasse ("wenig", "normal", "hoch") aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// return Talent als Zahl (-1=wenig, 0=normal, +1=hoch)
function getTalentFromHTML(cells, colIdxTal) {
    const __TEXT = getStringFromHTML(cells, colIdxTal);

    return parseInt((__TEXT === 'wenig') ? -1 : (__TEXT === 'hoch') ? +1 : 0, 10);
}

// Liest die Einzelskills aus der Spalte einer Zeile der Tabelle aus
// cells: Die Zellen einer Zeile
// colIdx: Liste von Spaltenindices der gesuchten Werte mit den Eintraegen
// 'Einz' (erste Spalte) und 'Zus' (Spalte hinter dem letzten Eintrag)
// return Skills als Array von Zahlen
function getSkillsFromHTML(cells, colIdx) {
    const __RESULT = [];

    for (let i = colIdx.Einz; i < colIdx.Zus; i++) {
        __RESULT[i - colIdx.Einz] = getIntFromHTML(cells, i);
    }

    return __RESULT;
}

// Liest aus, ob der Spieler Torwart oder Feldspieler ist
// cells: Die Zellen einer Zeile
// colIdxClass: Spaltenindex einer fuer TOR eingefaerbten Zelle
// return Angabe, der Spieler Torwart oder Feldspieler ist
function isGoalieFromHTML(cells, colIdxClass) {
    return (cells[colIdxClass].className === 'TOR');
}

// Liest einen String aus der Spalte einer Zeile der Tabelle aus, nachdem dieser konvertiert wurde
// cells: Die Zellen einer Zeile
// colIdxStr: Spaltenindex der gesuchten Werte
// convertFun: Funktion, die den Wert konvertiert
// return Spalteneintrag als String ("" fuer "nicht gefunden")
function convertStringFromHTML(cells, colIdxStr, convertFun = sameValue) {
    const __CELL = getValue(cells[colIdxStr], { });
    const __TEXT = convertFun(__CELL.textContent, __CELL, colIdxStr, 0);

    if (__TEXT !== undefined) {
        __CELL.innerHTML = __TEXT;
    }

    return getValue(__TEXT.toString(), "");
}

// Liest ein Array von String-Werten aus den Spalten ab einer Zeile der Tabelle aus, nachdem diese konvertiert wurden
// cells: Die Zellen einer Zeile
// colIdxArr: Erster Spaltenindex der gesuchten Werte
// arrOrLength: Entweder ein Datenarray zum Fuellen oder die Anzahl der zu lesenden Werte
// convertFun: Funktion, die die Werte konvertiert
// return Array mit Spalteneintraegen als String ("" fuer "nicht gefunden")
function convertArrayFromHTML(cells, colIdxArr, arrOrLength = 1, convertFun = sameValue) {
    const __ARR = ((typeof arrOrLength === 'number') ? { } : arrOrLength);
    const __LENGTH = getValue(__ARR.length, arrOrLength);
    const __RET = [];

    for (let index = 0, colIdx = colIdxArr; index < __LENGTH; index++, colIdx++) {
        const __CELL = getValue(cells[colIdx], { });
        const __TEXT = convertFun(getValue(__ARR[index], __CELL.textContent), __CELL, colIdx, index);

        if (__TEXT !== undefined) {
            __CELL.innerHTML = __TEXT;
        }

        __RET.push(getValue(__TEXT, "").toString());
    }

    return __RET;
}

// Konvertiert den Aufwertungstext einer Zelle auf der Jugend-Teamuebersicht
// value: Der Inhalt dieser Zeile ("+1 SKI +1 OPT" bzw. "+2 SKI)
// cell: Zelle, in der der Text stand (optional)
// return Der konvertierte String ("SKI OPT" bzw. "SKI SKI")
function convertAufwertung(value, cell = undefined) {
    if (value !== undefined) {
        value = value.replace(/\+2 (\w+)/, "$1 $1").replace(/\+1 /g, "");

        if (cell) {
            if (cell.className === 'TOR') {
                value = convertGoalieSkill(value);
            }

            cell.align = 'left';
        }
    }

    return value;
}

// Konvertiert die allgemeinen Skills in die eines Torwarts
// value: Ein Text, der die Skillnamen enthaelt
// return Der konvertierte String mit Aenderungen (z.B. "FAN" statt "KOB") oder unveraendert
function convertGoalieSkill(value) {
    if (value !== undefined) {
        value = value.replace(/\w+/g, getGoalieSkill);
    }

    return value;
}

// Konvertiert einen Aufwertungstext fuer einen Skillnamen in den fuer einen Torwart
// name: Allgemeiner Skillname (abgeleitet von den Feldspielern)
// return Der konvertierte String (z.B. "FAN" statt "KOB") oder unveraendert
function getGoalieSkill(name) {
    const __GOALIESKILLS = {
                               'SCH' : 'ABS',
                               'BAK' : 'STS',
                               'KOB' : 'FAN',
                               'ZWK' : 'STB',
                               'DEC' : 'SPL',
                               'GES' : 'REF'
                           };

    return getValue(__GOALIESKILLS[name], name);
}

// Liest die Aufwertungen eines Spielers aus und konvertiert je nachdem, ob der Spieler Torwart oder Feldspieler ist
// cells: Die Zellen einer Zeile
// colIdxAuf: Spaltenindex der gesuchten Aufwertungen
// shortForm: true = abgekuerzt, false = Originalform
// return Konvertierte Aufwertungen (kurze oder lange Form, aber in jedem Fall fuer Torwart konvertiert)
function getAufwertFromHTML(cells, colIdxAuf, shortForm = true) {
    const __ISGOALIE = isGoalieFromHTML(cells, colIdxAuf);

    return convertStringFromHTML(cells, colIdxAuf, (shortForm ? convertAufwertung : __ISGOALIE ? convertGoalieSkill : undefined));
}

// Identitaetsfunktion. Konvertiert nichts, sondern liefert einfach den Wert zurueck
// value: Der uebergebene Wert
// return Derselbe Wert
function sameValue(value) {
    return value;
}

// Existenzfunktion. Liefert zurueck, ob ein Wert belegt ist
// value: Der uebergebene Wert
// return Angabe ob Wert belegt ist
function existValue(value) {
    return !! value;
}

// Liefert den ganzzeiligen Anteil einer Zahl zurueck, indem alles hinter einem Punkt abgeschnitten wird
// value: Eine uebergebene Dezimalzahl
// return Der ganzzeilige Anteil dieser Zahl
function floorValue(value, dot = '.') {
    if ((value === 0) || (value && isFinite(value))) {
        const __VALUE = value.toString();
        const __INDEXDOT = (__VALUE ? __VALUE.indexOf(dot) : -1);

        return Number((~ __INDEXDOT) ? __VALUE.substring(0, __INDEXDOT) : __VALUE);
    } else {
        return value;
    }
}

// Liefert einen rechtsbuendigen Text zurueck, der links aufgefuellt wird
// value: Ein uebergebener Wert
// size: Zielbreite (clipping fuer < 0: Abschneiden, falls zu lang)
// char: Zeichen zum Auffuellen
// return Ein String, der mindestens |size| lang ist (oder genau, falls size < 0, also clipping)
function padLeft(value, size = 4, char = ' ') {
    const __SIZE = Math.abs(size);
    const __CLIP = (size < 0);
    const __VALUE = (value ? value.toString() : "");
    let i = __VALUE.length;
    let str = "";

    while (i < __SIZE) {
        str += char;
        i += char.length;
    }
    str = ((i > __SIZE) ? str.slice(0, __SIZE - __VALUE.length - 1) : str) + __VALUE;

    return (__CLIP ? str.slice(size) : str);
}

// Liefert eine rechtsbuendigen Zahl zurueck, der links (mit Nullen) aufgefuellt wird
// value: Eine uebergebene Zahl
// size: Zielbreite (Default: 2)
// char: Zeichen zum Auffuellen (Default: '0')
// forceClip: Abschneiden erzwingen, falls zu lang?
// return Eine Zahl als String, der mindestens 'size' lang ist (oder genau, falls size < 0, also clipping)
function padNumber(value, size = 2, char = '0') {
    if ((value === 0) || (value && isFinite(value))) {
        return padLeft(value, size, char);
    } else {
        return value;
    }
}

// Hilfsfunktionen **********************************************************************

// Sortiert das Positionsfeld per BubbleSort
function sortPositionArray(array) {
    const __TEMP = [];
    let transposed = true;
    // TOR soll immer die letzte Position im Feld sein, deshalb - 1
    let length = array.length - 1;

    while (transposed && (length > 1)) {
        transposed = false;
        for (let i = 0; i < length - 1; i++) {
            // Vergleich Opti-Werte:
            if (array[i][1] < array[i + 1][1]) {
                // vertauschen
                __TEMP[0] = array[i][0];
                __TEMP[1] = array[i][1];
                array[i][0] = array[i + 1][0];
                array[i][1] = array[i + 1][1];
                array[i + 1][0] = __TEMP[0];
                array[i + 1][1] = __TEMP[1];
                transposed = true;
            }
        }
        length--;
    }
}

// Schaut nach, ob der uebergebene Index zu einem trainierbaren Skill gehoert
// Die Indizes gehen von 0 (SCH) bis 16 (EIN)
function isTrainableSkill(idx) {
    const __TRAINABLESKILLS = getIdxTrainableSkills();
    const __IDX = parseInt(idx, 10);
    let result = false;

    for (let idxTrainable of __TRAINABLESKILLS) {
        if (__IDX === idxTrainable) {
            result = true;
            break;
        }
    }

    return result;
}

// Gibt alle Skill-Namen zurueck
function getAllSkillNames(isGoalie = false) {
    if (isGoalie) {
        return [ 'ABS', 'STS', 'FAN', 'STB', 'SPL', 'REF', 'FUQ', 'ERF', 'AGG', 'PAS', 'AUS', 'UEB', 'WID', 'SEL', 'DIS', 'ZUV', 'EIN' ];
    } else {
        return [ 'SCH', 'BAK', 'KOB', 'ZWK', 'DEC', 'GES', 'FUQ', 'ERF', 'AGG', 'PAS', 'AUS', 'UEB', 'WID', 'SEL', 'DIS', 'ZUV', 'EIN' ];
    }
}

// Gibt den Skill-Namen zu einem Index zurueck
function getSkillName(idx, isGoalie = false) {
    const __ALLNAMES = getAllSkillNames(isGoalie);

    return __ALLNAMES[idx];
}

// Gibt den Skill-Namen zu einem Index-Array zurueck
function getSkillNameArray(idxArr, isGoalie = false) {
    return (idxArr ? idxArr.map(function(item) {
                                    return getSkillName(item, isGoalie);
                                }) : idxArr);
}

// Gibt die Indizes aller Skills zurueck
function getIdxAllSkills() {
    return [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
}

// Gibt die Indizes der trainierbaren Skills zurueck
function getIdxTrainableSkills() {
    return [ 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 15 ];
}

// Gibt die Indizes der Fixskills zurueck
function getIdxFixSkills() {
    return [ 6, 7, 12, 13, 14, 16 ];
}

// Gibt die Indizes der Primaerskills zurueck
function getIdxPriSkills(pos) {
    switch (pos) {
        case 'TOR' : return [ 2, 3, 4, 5 ];
        case 'ABW' : return [ 2, 3, 4, 15 ];
        case 'DMI' : return [ 1, 4, 9, 11 ];
        case 'MIT' : return [ 1, 3, 9, 11 ];
        case 'OMI' : return [ 1, 5, 9, 11 ];
        case 'STU' : return [ 0, 2, 3, 5 ];
        default :    return [];
    }
}

// Gibt die Indizes der (trainierbaren) Sekundaerskills zurueck
function getIdxSecSkills(pos) {
    switch (pos) {
        case 'TOR' : return [ 0, 1, 8, 9, 10, 11, 15 ];
        case 'ABW' : return [ 0, 1, 5, 8, 9, 10, 11 ];
        case 'DMI' : return [ 0, 2, 3, 5, 8, 10, 15 ];
        case 'MIT' : return [ 0, 2, 4, 5, 8, 10, 15 ];
        case 'OMI' : return [ 0, 2, 3, 4, 8, 10, 15 ];
        case 'STU' : return [ 1, 4, 8, 9, 10, 11, 15 ];
        default :    return [];
    }
}

// Gibt die zur Position gehoerige Farbe zurueck
function getColor(pos) {
    switch (pos) {
        case 'TOR' : return "#FFFF00";
        case 'ABW' : return "#00FF00";
        case 'DMI' : return "#3366FF";
        case 'MIT' : return "#66FFFF";
        case 'OMI' : return "#FF66FF";
        case 'STU' : return "#FF0000";
        case 'LEI' : return "#FFFFFF";
        case "" :    return "#111166";  // osBlau
        default :    return "";
    }
}

// ==================== Ende Abschnitt genereller Code zur Anzeige der Jugend ====================

// ==================== Abschnitt fuer interne IDs auf den Seiten ====================

const __GAMETYPENRN = {    // "Blind FSS gesucht!"
        'unbekannt'  : -1,
        'reserviert' :  0,
        'Frei'       :  0,
        'spielfrei'  :  0,
        'Friendly'   :  1,
        'Liga'       :  2,
        'LP'         :  3,
        'OSEQ'       :  4,
        'OSE'        :  5,
        'OSCQ'       :  6,
        'OSC'        :  7,
        'Supercup'   : 10
    };

const __GAMETYPEALIASES = {
        'unbekannt'  :  "unbekannt",
        'reserviert' :  undefined,
        'Frei'       :  undefined,
        'spielfrei'  :  "",
        'Friendly'   :  "FSS",
        'Liga'       :  undefined,
        'LP'         :  "Pokal",
        'OSEQ'       :  undefined,
        'OSE'        :  undefined,
        'OSCQ'       :  undefined,
        'OSC'        :  undefined,
        'Supercup'   : "Super"
    };
const __GAMETYPES = reverseMapping(__GAMETYPENRN);

const __LIGANRN = {
        'unbekannt'  :  0,
        '1. Liga'    :  1,
        '2. Liga A'  :  2,
        '2. Liga B'  :  3,
        '3. Liga A'  :  4,
        '3. Liga B'  :  5,
        '3. Liga C'  :  6,
        '3. Liga D'  :  7
    };
const __LIGATYPES = reverseMapping(__LIGANRN);

const __LANDNRN = {
        'unbekannt'              :   0,
        'Albanien'               :  45,
        'Andorra'                :  95,
        'Armenien'               :  83,
        'Aserbaidschan'          : 104,
        'Belgien'                :  12,
        'Bosnien-Herzegowina'    :  66,
        'Bulgarien'              :  42,
        'D\xE4nemark'            :   8,
        'Deutschland'            :   6,
        'England'                :   1,
        'Estland'                :  57,
        'Far\xF6er'              :  68,
        'Finnland'               :  40,
        'Frankreich'             :  32,
        'Georgien'               :  49,
        'Griechenland'           :  30,
        'Irland'                 :   5,
        'Island'                 :  29,
        'Israel'                 :  23,
        'Italien'                :  10,
        'Kasachstan'             : 105,
        'Kroatien'               :  24,
        'Lettland'               :  97,
        'Liechtenstein'          :  92,
        'Litauen'                :  72,
        'Luxemburg'              :  93,
        'Malta'                  :  69,
        'Mazedonien'             :  86,
        'Moldawien'              :  87,
        'Niederlande'            :  11,
        'Nordirland'             :   4,
        'Norwegen'               :   9,
        '\xD6sterreich'          :  14,
        'Polen'                  :  25,
        'Portugal'               :  17,
        'Rum\xE4nien'            :  28,
        'Russland'               :  19,
        'San Marino'             :  98,
        'Schottland'             :   2,
        'Schweden'               :  27,
        'Schweiz'                :  37,
        'Serbien und Montenegro' :  41,
        'Slowakei'               :  70,
        'Slowenien'              :  21,
        'Spanien'                :  13,
        'Tschechien'             :  18,
        'T\xFCrkei'              :  39,
        'Ukraine'                :  20,
        'Ungarn'                 :  26,
        'Wales'                  :   3,
        'Weissrussland'          :  71,
        'Zypern'                 :  38
    };
const __LAENDER = reverseMapping(__LANDNRN);

const __TLALAND = {
        undefined : 'unbekannt',
        'ALB'     : 'Albanien',
        'AND'     : 'Andorra',
        'ARM'     : 'Armenien',
        'AZE'     : 'Aserbaidschan',
        'BEL'     : 'Belgien',
        'BIH'     : 'Bosnien-Herzegowina',
        'BUL'     : 'Bulgarien',
        'DEN'     : 'D\xE4nemark',
        'GER'     : 'Deutschland',
        'ENG'     : 'England',
        'EST'     : 'Estland',
        'FRO'     : 'Far\xF6er',
        'FIN'     : 'Finnland',
        'FRA'     : 'Frankreich',
        'GEO'     : 'Georgien',
        'GRE'     : 'Griechenland',
        'IRL'     : 'Irland',
        'ISL'     : 'Island',
        'ISR'     : 'Israel',
        'ITA'     : 'Italien',
        'KAZ'     : 'Kasachstan',
        'CRO'     : 'Kroatien',
        'LVA'     : 'Lettland',
        'LIE'     : 'Liechtenstein',
        'LTU'     : 'Litauen',
        'LUX'     : 'Luxemburg',
        'MLT'     : 'Malta',
        'MKD'     : 'Mazedonien',
        'MDA'     : 'Moldawien',
        'NED'     : 'Niederlande',
        'NIR'     : 'Nordirland',
        'NOR'     : 'Norwegen',
        'AUT'     : '\xD6sterreich',
        'POL'     : 'Polen',
        'POR'     : 'Portugal',
        'ROM'     : 'Rum\xE4nien',
        'RUS'     : 'Russland',
        'SMR'     : 'San Marino',
        'SCO'     : 'Schottland',
        'SWE'     : 'Schweden',
        'SUI'     : 'Schweiz',
        'SCG'     : 'Serbien und Montenegro',
        'SVK'     : 'Slowakei',
        'SVN'     : 'Slowenien',
        'ESP'     : 'Spanien',
        'CZE'     : 'Tschechien',
        'TUR'     : 'T\xFCrkei',
        'UKR'     : 'Ukraine',
        'HUN'     : 'Ungarn',
        'WAL'     : 'Wales',
        'BLR'     : 'Weissrussland',
        'CYP'     : 'Zypern'
    };
const __LANDTLAS = reverseMapping(__TLALAND);

// ==================== Abschnitt fuer Daten des Spielplans ====================

// Gibt die ID fuer den Namen eines Wettbewerbs zurueck
// gameType: Name des Wettbewerbs eines Spiels
// defValue: Default-Wert
// return OS2-ID fuer den Spieltyp (1 bis 7 oder 10), 0 fuer "spielfrei"/"Frei"/"reserviert", -1 fuer ungueltig
function getGameTypeID(gameType, defValue = __GAMETYPENRN.unbekannt) {
    return getValue(__GAMETYPENRN[gameType], defValue);
}

// Gibt den Namen eines Wettbewerbs zurueck
// id: OS2-ID des Wettbewerbs eines Spiels (1 bis 7 oder 10), 0 fuer "spielfrei"/"Frei"/"reserviert", -1 fuer ungueltig
// defValue: Default-Wert
// return Spieltyp fuer die uebergebene OS2-ID
function getGameType(id, defValue) {
    return getValue(__GAMETYPES[id], defValue);
}

// Gibt den alternativen (Kurznamen) fuer den Namen eines Wettbewerbs zurueck
// gameType: Name des Wettbewerbs eines Spiels
// return Normalerweise den uebergebenen Parameter, in Einzelfaellen eine Kurzversion
function getGameTypeAlias(gameType) {
    return getValue(__GAMETYPEALIASES[gameType], getValue(gameType, __GAMETYPEALIASES.unbekannt));
}

// Gibt den Namen des Landes mit dem uebergebenen Kuerzel (TLA) zurueck.
// tla: Kuerzel (TLA) des Landes
// defValue: Default-Wert
// return Name des Landes, 'unbekannt' fuer undefined
function getLandName(tla, defValue = __TLALAND[undefined]) {
    return getValue(__TLALAND[tla], defValue);
}

// Gibt die ID des Landes mit dem uebergebenen Namen zurueck.
// land: Name des Landes
// defValue: Default-Wert
// return OS2-ID des Landes, 0 fuer ungueltig
function getLandNr(land, defValue = __LANDNRN.unbekannt) {
    return getValue(__LANDNRN[land], defValue);
}

// Gibt die ID der Liga mit dem uebergebenen Namen zurueck.
// land: Name der Liga
// defValue: Default-Wert
// return OS2-ID der Liga, 0 fuer ungueltig
function getLigaNr(liga, defValue = __LIGANRN.unbekannt) {
    return getValue(__LIGANRN[liga], defValue);
}

// Kehrt das Mapping eines Objekts um und liefert ein neues Objekt zurueck.
// obj: Objekt mit key => value
// convFun: Konvertierfunktion fuer die Werte
// return Neues Objekt mit value => key (doppelte value-Werte fallen heraus!)
function reverseMapping(obj, convFun) {
    if (! obj) {
        return obj;
    }

    const __RET = { };

    for (let key in obj) {
        const __VALUE = obj[key];

        __RET[__VALUE] = (convFun ? convFun(key) : key);
    }

    return __RET;
}

// ==================== Abschnitt fuer sonstige Parameter ====================

// Formatiert eine Zelle um (mit einfachen Parametern)
// cell: Zu formatierende Zelle
// bold: Inhalt fett darstellen (true = ja, false = nein)
// color: Falls angegeben, die Schriftfarbe
// bgColor: Falls angegeben, die Hintergrundfarbe
// opacity: Falls angegeben, die Opazitaet
// return Die formatierte Zelle
function formatCell(cell, bold = true, color = undefined, bgColor = undefined, opacity = undefined) {
    if (cell) {
        if (bold) {
            cell.style.fontWeight = 'bold';
        }
        if (color) {
            cell.style.color = color;
        }
        if (bgColor) {
            cell.style.backgroundColor = bgColor;
        }
        if (opacity) {
        	cell.style.opacity = opacity;
        }
    }

    return cell;
}

// Ermittelt die auszugewaehlenden Werte eines Selects (Combo-Box) als Array zurueck
// element: 'select'-Element oder dessen Name auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// valFun: Funktion zur Ermittlung des Wertes eines 'option'-Eintrags (getSelectedOptionText, getSelectedValue, ...)
// defValue: Default-Wert, falls nichts selektiert ist
// return Array mit den Options-Werten
function getSelectionArray(element, valType = 'String', valFun = getSelectedValue, defValue = undefined) {
    const __SELECT = ((typeof element) === 'string' ? getValue(document.getElementsByName(element), [])[0] : element);

    return (__SELECT ? [].map.call(__SELECT.options, function(option) {
                                                         return this[valType](getValue(valFun(option), defValue));
                                                     }) : undefined);
}

// Ermittelt den ausgewaehlten Wert eines Selects (Combo-Box) und gibt diesen zurueck
// element: 'select'-Element oder dessen Name auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// valFun: Funktion zur Ermittlung des Wertes eines 'option'-Eintrags (getSelectedOptionText, getSelectedValue, ...)
// defValue: Default-Wert, falls nichts selektiert ist
// return Ausgewaehlter Wert
function getSelection(element, valType = 'String', valFun = getSelectedOptionText, defValue = undefined) {
    const __SELECT = ((typeof element) === 'string' ? getValue(document.getElementsByName(element), [])[0] : element);

    return this[valType](getValue(valFun(__SELECT), defValue));
}

// Ermittelt den ausgewaehlten Wert einer Combo-Box und gibt diesen zurueck
// comboBox: Alle 'option'-Eintraege der Combo-Box
// defValue: Default-Wert, falls nichts selektiert ist
// valType: Typ-Klasse der Optionswerte ('String', 'Number', ...)
// return Ausgewaehlter Wert
function getSelectionFromComboBox(comboBox, defValue = undefined, valType = 'String') {
    let selection;

    for (let i = 0; i < comboBox.length; i++) {
        const __ENTRY = comboBox[i];

        if (__ENTRY.outerHTML.match(/selected/)) {
            selection = __ENTRY.textContent;
        }
    }

    return this[valType](getValue(selection, defValue));
}

// Liefert den Text (textContent) einer selektierten Option
// element: 'select'-Element auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// return Wert der Selektion (textContent)
function getSelectedOptionText(element) {
    const __SELECTEDOPTIONS = getValue(element, { }).selectedOptions;
    const __OPTION = getValue(__SELECTEDOPTIONS, { })[0];

    return (__OPTION ? __OPTION.textContent : undefined);
}

// Liefert den 'value' einer selektierten Option
// element: 'select'-Element auf der HTML-Seite mit 'option'-Eintraegen der Combo-Box
// return Wert der Selektion ('value')
function getSelectedValue(element) {
    return getValue(element, { }).value;
}

// ==================== Ende Abschnitt fuer sonstige Parameter ====================

// ==================== Abschnitt fuer sonstige Parameter des Spielplans ====================

const __TEAMSEARCHHAUPT = {  // Parameter zum Team "<b>Willkommen im Managerbüro von TEAM</b><br>LIGA LAND<a href=..."
        'Zeile'  : 0,
        'Spalte' : 1,
        'start'  : " von ",
        'middle' : "</b><br>",
        'liga'   : ". Liga",
        'land'   : ' ',
        'end'    : "<a href="
    };

const __TEAMSEARCHTEAM = {  // Parameter zum Team "<b>TEAM - LIGA <a href=...>LAND</a></b>"
        'Zeile'  : 0,
        'Spalte' : 0,
        'start'  : "<b>",
        'middle' : " - ",
        'liga'   : ". Liga",
        'land'   : 'target="_blank">',
        'end'    : "</a></b>"
    };

// Ermittelt, wie das eigene Team heisst und aus welchem Land bzw. Liga es kommt (zur Unterscheidung von Erst- und Zweitteam)
// cell: Tabellenzelle mit den Parametern zum Team "startTEAMmiddleLIGA...landLANDend", LIGA = "#liga[ (A|B|C|D)]"
// teamSeach: Muster fuer die Suche, die Eintraege fuer 'start', 'middle', 'liga', 'land' und 'end' enthaelt
// return Im Beispiel { 'Team' : "TEAM", 'Liga' : "LIGA", 'Land' : "LAND", 'LdNr' : LAND-NUMMER, 'LgNr' : LIGA-NUMMER },
//        z.B. { 'Team' : "Choromonets Odessa", 'Liga' : "1. Liga", 'Land' : "Ukraine", 'LdNr' : 20, 'LgNr' : 1 }
function getTeamParamsFromTable(table, teamSearch = undefined) {
    const __TEAMSEARCH   = getValue(teamSearch, __TEAMSEARCHHAUPT);
    const __TEAMCELLROW  = getValue(__TEAMSEARCH.Zeile, 0);
    const __TEAMCELLCOL  = getValue(__TEAMSEARCH.Spalte, 0);
    const __TEAMCELLSTR  = (table === undefined) ? "" : table.rows[__TEAMCELLROW].cells[__TEAMCELLCOL].innerHTML;
    const __SEARCHSTART  = __TEAMSEARCH.start;
    const __SEARCHMIDDLE = __TEAMSEARCH.middle;
    const __SEARCHLIGA   = __TEAMSEARCH.liga;
    const __SEARCHLAND   = __TEAMSEARCH.land;
    const __SEARCHEND    = __TEAMSEARCH.end;
    const __INDEXSTART   = __TEAMCELLSTR.indexOf(__SEARCHSTART);
    const __INDEXEND     = __TEAMCELLSTR.indexOf(__SEARCHEND);

    let teamParams = __TEAMCELLSTR.substring(__INDEXSTART + __SEARCHSTART.length, __INDEXEND);
    const __INDEXLIGA = teamParams.indexOf(__SEARCHLIGA);
    const __INDEXMIDDLE = teamParams.indexOf(__SEARCHMIDDLE);

    let land = ((~ __INDEXLIGA) ? teamParams.substring(__INDEXLIGA + __SEARCHLIGA.length) : undefined);
    const __TEAMNAME = ((~ __INDEXMIDDLE) ? teamParams.substring(0, __INDEXMIDDLE) : undefined);
    let liga = (((~ __INDEXLIGA) && (~ __INDEXMIDDLE)) ? teamParams.substring(__INDEXMIDDLE + __SEARCHMIDDLE.length) : undefined);

    if (land !== undefined) {
        if (land.charAt(2) === ' ') {    // Land z.B. hinter "2. Liga A " statt "1. Liga "
            land = land.substr(2);
        }
        if (liga !== undefined) {
            liga = liga.substring(0, liga.length - land.length);
        }
        const __INDEXLAND = land.indexOf(__SEARCHLAND);
        if (~ __INDEXLAND) {
            land = land.substr(__INDEXLAND + __SEARCHLAND.length);
        }
    }

    const __TEAM = new Team(__TEAMNAME, land, liga);

    return __TEAM;
}

// Verarbeitet die URL der Seite und ermittelt die Nummer der gewuenschten Unterseite
// url: Adresse der Seite
// leafs: Liste von Filenamen mit der Default-Seitennummer (falls Query-Parameter nicht gefunden)
// item: Query-Parameter, der die Nummer der Unterseite angibt
// return Parameter aus der URL der Seite als Nummer
function getPageIdFromURL(url, leafs, item = 'page') {
    const __URI = new URI(url);
    const __LEAF = __URI.getLeaf();

    for (let leaf in leafs) {
        if (__LEAF === leaf) {
            const __DEFAULT = leafs[leaf];

            return getValue(__URI.getQueryPar(item), __DEFAULT);
        }
    }

    return -1;
}

// Gibt die laufende Nummer des ZATs im Text einer Zelle zurueck
// cell: Tabellenzelle mit der ZAT-Nummer im Text
// return ZAT-Nummer im Text
function getZATNrFromCell(cell) {
    const __TEXT = ((cell === undefined) ? [] : cell.textContent.split(' '));
    let ZATNr = 0;

    for (let i = 1; (ZATNr === 0) && (i < __TEXT.length); i++) {
        if (__TEXT[i - 1] === "ZAT") {
            if (__TEXT[i] !== "ist") {
                ZATNr = parseInt(__TEXT[i], 10);
            }
        }
    }

    return ZATNr;
}

// ==================== Ende Abschnitt fuer sonstige Parameter des Spielplans ====================

// ==================== Ende Abschnitt fuer Spielplan und ZATs ====================

// ==================== Hauptprogramm ====================

// Verarbeitet Ansicht "Haupt" (Managerbuero) zur Ermittlung des aktuellen ZATs
function procHaupt() {
    const __TEAMPARAMS = getTeamParamsFromTable(getTable(1), __TEAMSEARCHHAUPT);  // Link mit Team, Liga, Land...

    return buildOptions(__OPTCONFIG, __OPTSET, {
                            'teamParams' : __TEAMPARAMS,
//                            'menuAnchor' : getTable(0, 'div'),
                            'hideMenu'   : true,
                            'showForm'   : {
                                               'zeigeWarnung'         : true,
                                               'zeigeWarnungMonat'    : true,
                                               'zeigeWarnungHome'     : true,
                                               'zeigeWarnungDialog'   : true,
                                               'zeigeWarnungAufstieg' : true,
                                               'zeigeWarnungLegende'  : true,
                                               'ziehAnz'              : true,
                                               'showForm'             : true
                                           }
                        }).then(async optSet => {
            const __ZATCELL = getProp(getProp(getRows(0), 2), 'cells', { })[0];
            const __NEXTZAT = getZATNrFromCell(__ZATCELL);  // "Der naechste ZAT ist ZAT xx und ..."
            const __CURRZAT = __NEXTZAT - 1;
            const __DATAZAT = getOptValue(__OPTSET.datenZat);

            // Stand der alten Daten merken...
            setOpt(__OPTSET.oldDatenZat, __DATAZAT, false);

            if (__CURRZAT >= 0) {
                __LOG[2]("Aktueller ZAT: " + __CURRZAT);

                // Neuen aktuellen ZAT speichern...
                setOpt(__OPTSET.aktuellerZat, __CURRZAT, false);

                if (__CURRZAT !== __DATAZAT) {
                    __LOG[2](__LOG.changed(__DATAZAT, __CURRZAT));

                    // ... und ZAT-bezogene Daten als veraltet markieren (ausser 'skills', 'positions' und 'ziehAnz')
                    await __TEAMCLASS.deleteOptions({
                                                    'skills'      : true,
                                                    'positions'   : true,
                                                    'datenZat'    : true,
                                                    'oldDatenZat' : true,
                                                    'ziehAnz'     : (__CURRZAT > __DATAZAT)  // nur loeschen, wenn < __DATAZAT
                                                }).catch(defaultCatch);

                    // Neuen Daten-ZAT speichern...
                    setOpt(__OPTSET.datenZat, __CURRZAT, false);
                }
            }

            const __MSG = new WarnDrawMessage(optSet, __CURRZAT);
            const __MSGAUFSTIEG = new WarnDrawMessageAufstieg(optSet, __CURRZAT);
            const __ANCHOR = getTable(0, 'tbody');

            __MSG.showMessage(__ANCHOR, 'tr', true);
            __MSG.showDialog(showAlert);
            __MSGAUFSTIEG.showMessage(__ANCHOR, 'tr', true);
        });
}

// Verarbeitet Ansicht "Optionen" zur Ermittlung der Jugendfoerderung
function procOptionen() {
    return buildOptions(__OPTCONFIG, __OPTSET, {
                            'menuAnchor'  : getTable(0, 'div'),
                            'hideMenu'    : true,
                            'getDonation' : true,
                            'showForm'    : {
                                                'foerderung'           : true,
                                                'zeigeWarnung'         : true,
                                                'zeigeWarnungMonat'    : true,
                                                'zeigeWarnungHome'     : true,
                                                'zeigeWarnungDialog'   : true,
                                                'zeigeWarnungAufstieg' : true,
                                                'zeigeWarnungLegende'  : true,
                                                'ziehAnz'              : true,
                                                'showForm'             : true
                                            }
        });
}

// Verarbeitet Ansicht "Teamuebersicht"
function procTeamuebersicht() {
    const __ROWOFFSETUPPER = 1;     // Header-Zeile
    const __ROWOFFSETLOWER = 1;     // Ziehen-Button

    const __COLUMNINDEX = {
            'Age'   : 0,
            'Geb'   : 1,
            'Flg'   : 2,
            'Land'  : 3,
            'U'     : 4,
            'Skill' : 5,
            'Tal'   : 6,
            'Akt'   : 7,
            'Auf'   : 8,
            'Zus'   : 9
        };

    if (getElement('transfer') !== undefined) {
        __LOG[2]("Ziehen-Seite");
    } else if (getRows(1) === undefined) {
        __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else {
        return buildOptions(__OPTCONFIG, __OPTSET, {
                                'menuAnchor' : getTable(0, 'div'),
                                'showForm'   : {
                                                   'kennzeichenEnde'      : true,
                                                   'shortAufw'            : true,
                                                   'sepStyle'             : true,
                                                   'sepColor'             : true,
                                                   'sepWidth'             : true,
                                                   'saison'               : true,
                                                   'aktuellerZat'         : true,
                                                   'foerderung'           : true,
                                                   'team'                 : true,
                                                   'zeigeJahrgang'        : true,
                                                   'zeigeUxx'             : true,
                                                   'zeigeWarnung'         : true,
                                                   'zeigeWarnungMonat'    : true,
                                                   'zeigeWarnungHome'     : true,
                                                   'zeigeWarnungDialog'   : true,
                                                   'zeigeWarnungAufstieg' : true,
                                                   'zeigeWarnungLegende'  : true,
                                                   'zeigeBalken'          : true,
                                                   'absBalken'            : true,
                                                   'zeigeId'              : true,
                                                   'ersetzeAlter'         : true,
                                                   'zeigeAlter'           : true,
                                                   'zeigeQuote'           : true,
                                                   'zeigePosition'        : true,
                                                   'zeigeZatDone'         : true,
                                                   'zeigeZatLeft'         : true,
                                                   'zeigeFixSkills'       : true,
                                                   'zeigeTrainiert'       : true,
                                                   'zeigeAnteilPri'       : true,
                                                   'zeigeAnteilSec'       : true,
                                                   'zeigePrios'           : true,
                                                   'anzahlOpti'           : true,
                                                   'anzahlMW'             : true,
                                                   'zeigeTrainiertEnde'   : true,
                                                   'zeigeAnteilPriEnde'   : true,
                                                   'zeigeAnteilSecEnde'   : true,
                                                   'zeigePriosEnde'       : true,
                                                   'zeigeSkillEnde'       : true,
                                                   'anzahlOptiEnde'       : true,
                                                   'anzahlMWEnde'         : true,
                                                   'ziehAnz'              : true,
                                                   'zatAges'              : true,
                                                   'trainiert'            : true,
                                                   'positions'            : true,
                                                   'skills'               : true,
                                                   'reset'                : true,
                                                   'showForm'             : true
                                               },
                                'formWidth'  : 1
                            }).then(optSet => {
                const __ROWS = getRows(1);
                const __HEADERS = __ROWS[0];
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"

                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 1);
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, {
                                                    'Default'            : true,
                                                    'ersetzeSkills'      : false,
                                                    'zeigeGeb'           : false,
                                                    'zeigeSkill'         : false,
                                                    'zeigeTal'           : false,
                                                    'zeigeAufw'          : false
                                                });

                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);

                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
                    if (__ROWS[i].cells.length > 1) {
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
                    } else {
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }

                // Format der Trennlinie zwischen den Jahrgaengen...
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);

                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
                }

                const __CURRZAT = getOptValue(__OPTSET.datenZat);
                const __MSG = new WarnDrawMessage(__OPTSET, __CURRZAT);
                const __MSGAUFSTIEG = new WarnDrawMessageAufstieg(__OPTSET, __CURRZAT);
                const __ANCHOR = getTable(0, 'div');
                const __SEARCH = '<form method="POST">';

                // Kompaktere Darstellung und ohne Links...
                __MSG.out.top = false;
                __MSG.out.label = false;
                __MSG.out.link = false;
                __MSG.out.bottom = false;
                __MSGAUFSTIEG.out.label = false;
                __MSGAUFSTIEG.out.link = false;
                __MSGAUFSTIEG.out.bottom = false;

                __MSG.setOptionLegende();
                __MSGAUFSTIEG.setOptionLegende();

                __MSG.showMessage(__ANCHOR, 'p', __SEARCH);
                __MSGAUFSTIEG.showMessage(__ANCHOR, 'p', __SEARCH);
            });
    }

    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}

// Verarbeitet Ansicht "Spielereinzelwerte"
function procSpielereinzelwerte() {
    const __ROWOFFSETUPPER = 1;     // Header-Zeile
    const __ROWOFFSETLOWER = 0;

    const __COLUMNINDEX = {
            'Flg'   : 0,
            'Land'  : 1,
            'U'     : 2,
            'X'     : 3,
            'Age'   : 4,
            'Einz'  : 5,    // ab hier die Einzelskills
            'SCH'   : 5,
            'ABS'   : 5,    // TOR
            'BAK'   : 6,
            'STS'   : 6,    // TOR
            'KOB'   : 7,
            'FAN'   : 7,    // TOR
            'ZWK'   : 8,
            'STB'   : 8,    // TOR
            'DEC'   : 9,
            'SPL'   : 9,    // TOR
            'GES'   : 10,
            'REF'   : 10,   // TOR
            'FUQ'   : 11,
            'ERF'   : 12,
            'AGG'   : 13,
            'PAS'   : 14,
            'AUS'   : 15,
            'UEB'   : 16,
            'WID'   : 17,
            'SEL'   : 18,
            'DIS'   : 19,
            'ZUV'   : 20,
            'EIN'   : 21,
            'Zus'   : 22     // Zusaetze hinter den Einzelskills
        };

    if (getRows(1) === undefined) {
        __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else {
        return buildOptions(__OPTCONFIG, __OPTSET, {
                                'menuAnchor' : getTable(0, 'div'),
                                'hideForm'   : {
                                                   'zeigeWarnung'         : false,
                                                   'zeigeWarnungMonat'    : false,
                                                   'zeigeWarnungHome'     : false,
                                                   'zeigeWarnungDialog'   : false,
                                                   'zeigeWarnungAufstieg' : false,
                                                   'zeigeWarnungLegende'  : false,
                                                   'ziehAnz'              : true,
                                                   'zatAges'              : true,
                                                   'trainiert'            : true,
                                                   'positions'            : true,
                                                   'skills'               : true,
                                                   'shortAufw'            : true
                                               },
                                'formWidth'  : 1
                            }).then(optSet => {
                const __ROWS = getRows(1);
                const __HEADERS = __ROWS[0];
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"

                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 2);
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, true);

                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);

                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
                    if (__ROWS[i].cells.length > 1) {
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
                    } else {
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }

                // Format der Trennlinie zwischen den Jahrgaengen...
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);

                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
                }
            });
    }

    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}

// Verarbeitet Ansicht "Opt. Skill"
function procOptSkill() {
    const __ROWOFFSETUPPER = 1;     // Header-Zeile
    const __ROWOFFSETLOWER = 0;

    const __COLUMNINDEX = {
            'Flg'   : 0,
            'Land'  : 1,
            'U'     : 2,
            'Age'   : 3,
            'Skill' : 4,
            'TOR'   : 5,
            'ABW'   : 6,
            'DMI'   : 7,
            'MIT'   : 8,
            'OMI'   : 9,
            'STU'   : 10,
            'Zus'   : 11     // Zusaetze hinter den OptSkills
        };

    if (getRows(1) === undefined) {
        __LOG[2]("Diese Seite ist ohne Team nicht verf\xFCgbar!");
    } else {
        return buildOptions(__OPTCONFIG, __OPTSET, {
                                'menuAnchor' : getTable(0, 'div'),
                                'showForm'   : {
                                                   'kennzeichenEnde'      : true,
                                                   'sepStyle'             : true,
                                                   'sepColor'             : true,
                                                   'sepWidth'             : true,
                                                   'saison'               : true,
                                                   'aktuellerZat'         : true,
                                                   'foerderung'           : true,
                                                   'team'                 : true,
                                                   'zeigeJahrgang'        : true,
                                                   'zeigeUxx'             : true,
                                                   'zeigeWarnung'         : false,
                                                   'zeigeWarnungMonat'    : false,
                                                   'zeigeWarnungHome'     : false,
                                                   'zeigeWarnungDialog'   : false,
                                                   'zeigeWarnungAufstieg' : false,
                                                   'zeigeWarnungLegende'  : false,
                                                   'zeigeBalken'          : true,
                                                   'absBalken'            : true,
                                                   'zeigeId'              : true,
                                                   'ersetzeAlter'         : true,
                                                   'zeigeAlter'           : true,
                                                   'zeigeQuote'           : true,
                                                   'zeigePosition'        : true,
                                                   'zeigeZatDone'         : true,
                                                   'zeigeZatLeft'         : true,
                                                   'zeigeFixSkills'       : true,
                                                   'zeigeTrainiert'       : true,
                                                   'zeigeAnteilPri'       : true,
                                                   'zeigeAnteilSec'       : true,
                                                   'zeigePrios'           : true,
                                                   'zeigeAufw'            : true,
                                                   'zeigeGeb'             : true,
                                                   'zeigeTal'             : true,
                                                   'anzahlOpti'           : true,
                                                   'anzahlMW'             : true,
                                                   'zeigeTrainiertEnde'   : true,
                                                   'zeigeAnteilPriEnde'   : true,
                                                   'zeigeAnteilSecEnde'   : true,
                                                   'zeigePriosEnde'       : true,
                                                   'zeigeSkillEnde'       : true,
                                                   'anzahlOptiEnde'       : true,
                                                   'anzahlMWEnde'         : true,
                                                   'zatAges'              : true,
                                                   'trainiert'            : true,
                                                   'positions'            : true,
                                                   'skills'               : true,
                                                   'reset'                : true,
                                                   'showForm'             : true
                                               },
                                'formWidth'  : 1
                            }).then(optSet => {
                const __ROWS = getRows(1);
                const __HEADERS = __ROWS[0];
                const __TITLECOLOR = getColor('LEI');  // "#FFFFFF"

                const __PLAYERS = init(__ROWS, __OPTSET, __COLUMNINDEX, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 3);
                const __COLMAN = new ColumnManager(__OPTSET, __COLUMNINDEX, {
                                                    'Default'            : true,
                                                    'ersetzeSkills'      : false,
                                                    'zeigeSkill'         : false
                                                });

                __COLMAN.addTitles(__HEADERS, __TITLECOLOR);

                for (let i = __ROWOFFSETUPPER, j = 0; i < __ROWS.length - __ROWOFFSETLOWER; i++) {
                    if (__ROWS[i].cells.length > 1) {
                        __COLMAN.addValues(__PLAYERS[j++], __ROWS[i], __TITLECOLOR);
                    } else {
                        __COLMAN.setGroupTitle(__ROWS[i]);
                    }
                }

                // Format der Trennlinie zwischen den Jahrgaengen...
                if (! __COLMAN.gt) {
                    const __BORDERSTRING = getOptValue(__OPTSET.sepStyle) + ' ' + getOptValue(__OPTSET.sepColor) + ' ' + getOptValue(__OPTSET.sepWidth);

                    separateGroups(__ROWS, __BORDERSTRING, __COLUMNINDEX.Land, __ROWOFFSETUPPER, __ROWOFFSETLOWER, 0, 0, existValue);
                }
            });
    }

    // Promise fuer alle Faelle ohne Rueckgabewert...
    return Promise.resolve();
}

(() => {
    (async () => {
        try {
            // URL-Legende:
            // page=0: Managerbuero
            // page=1: Teamuebersicht
            // page=2: Spielereinzelwerte
            // page=3: Opt. Skill
            // page=4: Optionen

            // Verzweige in unterschiedliche Verarbeitungen je nach Wert von page:
            switch (getPageIdFromURL(window.location.href, {
                                                               'haupt.php' : 0,  // Ansicht "Haupt" (Managerbuero)
                                                               'ju.php'    : 1   // Ansicht "Jugendteam"
                                                           }, 'page')) {
                case 0  : await procHaupt().catch(defaultCatch); break;
                case 1  : await procTeamuebersicht().catch(defaultCatch); break;
                case 2  : await procSpielereinzelwerte().catch(defaultCatch); break;
                case 3  : await procOptSkill().catch(defaultCatch); break;
                case 4  : await procOptionen().catch(defaultCatch); break;
                default : break;
            }

            return 'OK';
        } catch (ex) {
            return defaultCatch(ex);
        }
    })().then(rc => {
            __LOG[1]('SCRIPT END', __DBMOD.Name, '(' + rc + ')');
        })
})();

// *** EOF ***